mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-19 17:15:49 +00:00
Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4593147b65 | ||
|
|
020d1adc55 | ||
|
|
389b5408b1 | ||
|
|
037dbb5670 | ||
|
|
6af0d55ca9 | ||
|
|
0917eb2742 | ||
|
|
f8be7a7649 | ||
|
|
5b87b12535 | ||
|
|
8908e8b16a | ||
|
|
4f7af1e57a | ||
|
|
3af55cc5b4 | ||
|
|
ac5d8af4f9 | ||
|
|
01cd7539f9 | ||
|
|
102864525c | ||
|
|
e6254e23f2 | ||
|
|
5f3c2f781e | ||
|
|
6c73791cab | ||
|
|
f76e3ff94b | ||
|
|
7f0fc1b8ef | ||
|
|
d18eb7e4e4 | ||
|
|
d3377cd45e | ||
|
|
64a5a9f1bc | ||
|
|
32afd7200a | ||
|
|
ffa531f9ca | ||
|
|
05deb89451 | ||
|
|
6f807823b9 | ||
|
|
2f594ca7c9 | ||
|
|
ecae6e5ead | ||
|
|
34ab6ed7ee | ||
|
|
5ba9d6e118 | ||
|
|
c47a67975f | ||
|
|
6563d23f38 | ||
|
|
3a46c3302b | ||
|
|
1c97593360 | ||
|
|
95416cd553 | ||
|
|
d6cd0611d4 | ||
|
|
aad2cd8739 | ||
|
|
4cc0149317 | ||
|
|
836ee29b78 | ||
|
|
806b57f959 | ||
|
|
e827c1477c | ||
|
|
b827a4680d | ||
|
|
3fd124f76d | ||
|
|
3e8863f6ce | ||
|
|
9b026572cf | ||
|
|
6b34a3ae13 | ||
|
|
8b64180136 | ||
|
|
688ae4da10 | ||
|
|
0785da7223 | ||
|
|
2f1aad3e63 | ||
|
|
754b591e4f | ||
|
|
2b9d2d044c | ||
|
|
511eef54bb | ||
|
|
e705ae8e48 | ||
|
|
c7926d0bc0 | ||
|
|
034bc5e228 | ||
|
|
a39d07a68a | ||
|
|
81c9b4450b | ||
|
|
fc3ea2dd4b | ||
|
|
fe7a5f1813 | ||
|
|
3cd1b59a6c | ||
|
|
7ec6989c99 | ||
|
|
ee4d7a02a9 | ||
|
|
d6fd1c7ff0 | ||
|
|
5fbd5e8518 | ||
|
|
c31882cb92 | ||
|
|
81d47f7512 | ||
|
|
0baa204ce9 | ||
|
|
660e5ad101 | ||
|
|
aebf52efb2 | ||
|
|
c83a1db0c8 | ||
|
|
865d3e08e7 | ||
|
|
91ee6dc7cb | ||
|
|
7708bb9af2 | ||
|
|
03b7a34793 | ||
|
|
f3eb4f055d | ||
|
|
c61575ac9a | ||
|
|
f8796386dc | ||
|
|
ad38481312 | ||
|
|
937285ea3b | ||
|
|
328eeb8233 | ||
|
|
02239c8f2d | ||
|
|
70b3db074a | ||
|
|
d560cd9cc8 | ||
|
|
7526c4d969 | ||
|
|
766ef54b31 | ||
|
|
6b5535e60a | ||
|
|
fe00cfb09b | ||
|
|
2b4d6160c4 | ||
|
|
57029b1a40 | ||
|
|
61489077d7 | ||
|
|
4621933e5b | ||
|
|
fb76b2d500 | ||
|
|
9f6957ef3f | ||
|
|
d6e05d4a1a | ||
|
|
ea67b9760d | ||
|
|
3a503f12c8 | ||
|
|
2b7ad7cb9b | ||
|
|
9f38e19b81 | ||
|
|
73718a5dc5 | ||
|
|
bb9d00a0b3 | ||
|
|
3a1be63a40 | ||
|
|
4daaf0a647 | ||
|
|
f5dacd28e1 | ||
|
|
f65d3a5a98 | ||
|
|
13de2c6ca0 | ||
|
|
6cf29d5145 | ||
|
|
182710b86c | ||
|
|
4a1387ea83 | ||
|
|
c53cee31f5 | ||
|
|
222b9734ca | ||
|
|
c9ba393ce7 | ||
|
|
aa40016ec8 | ||
|
|
dc49304aa5 | ||
|
|
bb7b667467 | ||
|
|
d171850255 | ||
|
|
1207501405 | ||
|
|
2a2bf531ee | ||
|
|
9d724d34e1 | ||
|
|
618a566283 | ||
|
|
6804facabc | ||
|
|
98dd6bb949 | ||
|
|
f0e9aa0b8f | ||
|
|
68a16ef0e2 | ||
|
|
012775833a | ||
|
|
e4567a2b24 | ||
|
|
6c0775b120 | ||
|
|
9fbaede59f | ||
|
|
fd75cca266 | ||
|
|
9f904f8f47 | ||
|
|
78e1194ebb | ||
|
|
e04283c1fb | ||
|
|
a6742f395a | ||
|
|
9fba92d879 | ||
|
|
daa4354047 | ||
|
|
ec88053df0 | ||
|
|
08e259327b | ||
|
|
98384ac236 | ||
|
|
5f9058c84f | ||
|
|
979fdedbbe | ||
|
|
2463b99479 | ||
|
|
ff547a258d | ||
|
|
251ceeedba | ||
|
|
c1422be269 | ||
|
|
538fc9b365 | ||
|
|
b172d450e3 | ||
|
|
5c695ca652 | ||
|
|
e7ce8c8ddb | ||
|
|
4b9bbbc34b | ||
|
|
820e302aac | ||
|
|
ebaabf6150 | ||
|
|
a7bea936c5 | ||
|
|
38378fe36f | ||
|
|
7f13adbd05 | ||
|
|
74ba6d7825 | ||
|
|
7fd4015f17 | ||
|
|
589a8702aa | ||
|
|
91af54abf1 |
20
.github/workflows/docker.yml
vendored
20
.github/workflows/docker.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Release 3X-UI dockerhub
|
name: Release 3X-UI for Docker
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -11,15 +11,15 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the code
|
- name: Check out the code
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.0.0
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v3.0.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -27,15 +27,15 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5.5.0
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v5.1.0
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6
|
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
44
.github/workflows/release.yml
vendored
44
.github/workflows/release.yml
vendored
@@ -15,23 +15,31 @@ jobs:
|
|||||||
- arm64
|
- arm64
|
||||||
- armv7
|
- armv7
|
||||||
- armv6
|
- armv6
|
||||||
|
- 386
|
||||||
|
- armv5
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.22'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
sudo apt install gcc-aarch64-linux-gnu
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ] || [ "${{ matrix.platform }}" == "armv6" ]; then
|
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
sudo apt install gcc-arm-linux-gnueabihf
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
||||||
|
sudo apt install gcc-arm-linux-gnueabihf
|
||||||
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
|
sudo apt install gcc-i686-linux-gnu
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||||
|
sudo apt install gcc-arm-linux-gnueabi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build x-ui
|
- name: Build x-ui
|
||||||
@@ -42,10 +50,21 @@ jobs:
|
|||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
export GOARCH=arm64
|
export GOARCH=arm64
|
||||||
export CC=aarch64-linux-gnu-gcc
|
export CC=aarch64-linux-gnu-gcc
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ] || [ "${{ matrix.platform }}" == "armv6" ]; then
|
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||||
export GOARCH=arm
|
export GOARCH=arm
|
||||||
export GOARM=7
|
export GOARM=7
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
export CC=arm-linux-gnueabihf-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
||||||
|
export GOARCH=arm
|
||||||
|
export GOARM=6
|
||||||
|
export CC=arm-linux-gnueabihf-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
|
export GOARCH=386
|
||||||
|
export CC=i686-linux-gnu-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||||
|
export GOARCH=arm
|
||||||
|
export GOARM=5
|
||||||
|
export CC=arm-linux-gnueabi-gcc
|
||||||
fi
|
fi
|
||||||
go build -o xui-release -v main.go
|
go build -o xui-release -v main.go
|
||||||
|
|
||||||
@@ -58,7 +77,7 @@ jobs:
|
|||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.7/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.9/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
wget ${Xray_URL}Xray-linux-64.zip
|
wget ${Xray_URL}Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
@@ -75,6 +94,14 @@ jobs:
|
|||||||
wget ${Xray_URL}Xray-linux-arm32-v6.zip
|
wget ${Xray_URL}Xray-linux-arm32-v6.zip
|
||||||
unzip Xray-linux-arm32-v6.zip
|
unzip Xray-linux-arm32-v6.zip
|
||||||
rm -f Xray-linux-arm32-v6.zip
|
rm -f Xray-linux-arm32-v6.zip
|
||||||
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
|
wget ${Xray_URL}Xray-linux-32.zip
|
||||||
|
unzip Xray-linux-32.zip
|
||||||
|
rm -f Xray-linux-32.zip
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||||
|
wget ${Xray_URL}Xray-linux-arm32-v5.zip
|
||||||
|
unzip Xray-linux-arm32-v5.zip
|
||||||
|
rm -f Xray-linux-arm32-v5.zip
|
||||||
fi
|
fi
|
||||||
rm -f geoip.dat geosite.dat
|
rm -f geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -89,12 +116,11 @@ jobs:
|
|||||||
- name: Package
|
- name: Package
|
||||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||||
|
|
||||||
- name: Upload
|
- name: Upload files to GH release
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
overwrite: true
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
.cache
|
.cache
|
||||||
.sync*
|
.sync*
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
*.log
|
||||||
access.log
|
access.log
|
||||||
error.log
|
error.log
|
||||||
tmp
|
tmp
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
case $1 in
|
case $1 in
|
||||||
amd64)
|
amd64)
|
||||||
ARCH="64"
|
ARCH="64"
|
||||||
FNAME="amd64"
|
FNAME="amd64"
|
||||||
;;
|
;;
|
||||||
|
i386)
|
||||||
|
ARCH="32"
|
||||||
|
FNAME="i386"
|
||||||
|
;;
|
||||||
armv8 | arm64 | aarch64)
|
armv8 | arm64 | aarch64)
|
||||||
ARCH="arm64-v8a"
|
ARCH="arm64-v8a"
|
||||||
FNAME="arm64"
|
FNAME="arm64"
|
||||||
@@ -22,19 +25,16 @@ case $1 in
|
|||||||
FNAME="amd64"
|
FNAME="amd64"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
|
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.9/Xray-linux-${ARCH}.zip"
|
||||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip"
|
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat geoip_IR.dat geosite_IR.dat geoip_VN.dat geosite_VN.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||||
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||||
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
|
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
|
||||||
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
|
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
|
||||||
|
cd ../../
|
||||||
17
Dockerfile
17
Dockerfile
@@ -1,11 +1,9 @@
|
|||||||
# ========================================================
|
# ========================================================
|
||||||
# Stage: Builder
|
# Stage: Builder
|
||||||
# ========================================================
|
# ========================================================
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
|
FROM golang:1.22-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV CGO_ENABLED=1
|
|
||||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
|
||||||
|
|
||||||
RUN apk --no-cache --update add \
|
RUN apk --no-cache --update add \
|
||||||
build-base \
|
build-base \
|
||||||
@@ -15,6 +13,8 @@ RUN apk --no-cache --update add \
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||||
RUN go build -o build/x-ui main.go
|
RUN go build -o build/x-ui main.go
|
||||||
RUN ./DockerInit.sh "$TARGETARCH"
|
RUN ./DockerInit.sh "$TARGETARCH"
|
||||||
|
|
||||||
@@ -28,11 +28,13 @@ WORKDIR /app
|
|||||||
RUN apk add --no-cache --update \
|
RUN apk add --no-cache --update \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
fail2ban
|
fail2ban \
|
||||||
|
bash
|
||||||
|
|
||||||
|
COPY --from=builder /app/build/ /app/
|
||||||
|
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
||||||
|
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
||||||
|
|
||||||
COPY --from=builder /app/build/ /app/
|
|
||||||
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
|
||||||
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
|
||||||
|
|
||||||
# Configure fail2ban
|
# Configure fail2ban
|
||||||
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
||||||
@@ -47,4 +49,5 @@ RUN chmod +x \
|
|||||||
/usr/bin/x-ui
|
/usr/bin/x-ui
|
||||||
|
|
||||||
VOLUME [ "/etc/x-ui" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
|
CMD [ "./x-ui" ]
|
||||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||||
|
|||||||
148
README.md
148
README.md
@@ -1,5 +1,7 @@
|
|||||||
# 3X-UI
|
# 3X-UI
|
||||||
|
|
||||||
|
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||||
|
|
||||||
**An Advanced Web Panel • Built on Xray Core**
|
**An Advanced Web Panel • Built on Xray Core**
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||||
@@ -12,8 +14,7 @@
|
|||||||
|
|
||||||
**If this project is helpful to you, you may wish to give it a**:star2:
|
**If this project is helpful to you, you may wish to give it a**:star2:
|
||||||
|
|
||||||
<a href="#">
|
<p align="left"><a href="#"><img width="125" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1" alt="Image"></a></p>
|
||||||
<img width="125" alt="image" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1.jpg"></a>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
|
|
||||||
@@ -25,11 +26,39 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||||||
|
|
||||||
## Install Custom Version
|
## Install Custom Version
|
||||||
|
|
||||||
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.1`:
|
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.5`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.1
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.5
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SSL Certificate
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for SSL Certificate</summary>
|
||||||
|
|
||||||
|
### Cloudflare
|
||||||
|
|
||||||
|
The 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
|
||||||
|
|
||||||
|
**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
|
||||||
|
|
||||||
|
|
||||||
|
### Certbot
|
||||||
|
```
|
||||||
|
apt-get install certbot -y
|
||||||
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
|
certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Manual Install & Upgrade
|
## Manual Install & Upgrade
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -41,7 +70,17 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
[[ "${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" ;;
|
||||||
|
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||||
|
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||||
|
*) XUI_ARCH="amd64" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -49,7 +88,16 @@ wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
[[ "${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" ;;
|
||||||
|
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||||
|
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||||
|
*) XUI_ARCH="amd64" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
cd /root/
|
cd /root/
|
||||||
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||||
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
@@ -106,10 +154,19 @@ systemctl restart x-ui
|
|||||||
update to latest version
|
update to latest version
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd 3x-ui
|
cd 3x-ui
|
||||||
docker compose down
|
docker compose down
|
||||||
docker compose pull 3x-ui
|
docker compose pull 3x-ui
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
remove 3x-ui from docker
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker stop 3x-ui
|
||||||
|
docker rm 3x-ui
|
||||||
|
cd --
|
||||||
|
rm -r 3x-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@@ -127,6 +184,25 @@ update to latest version
|
|||||||
- AlmaLinux 9+
|
- AlmaLinux 9+
|
||||||
- Rockylinux 9+
|
- Rockylinux 9+
|
||||||
|
|
||||||
|
## Supported Architectures and Devices
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Supported Architectures and devices details</summary>
|
||||||
|
|
||||||
|
Our platform offers compatibility with a diverse range of architectures and devices, ensuring flexibility across various computing environments. The following are key architectures that we support:
|
||||||
|
|
||||||
|
- **amd64**: This prevalent architecture is the standard for personal computers and servers, accommodating most modern operating systems seamlessly.
|
||||||
|
|
||||||
|
- **x86 / i386**: Widely adopted in desktop and laptop computers, this architecture enjoys broad support from numerous operating systems and applications, including but not limited to Windows, macOS, and Linux systems.
|
||||||
|
|
||||||
|
- **armv8 / arm64 / aarch64**: Tailored for contemporary mobile and embedded devices, such as smartphones and tablets, this architecture is exemplified by devices like Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, and more.
|
||||||
|
|
||||||
|
- **armv7 / arm / arm32**: Serving as the architecture for older mobile and embedded devices, it remains widely utilized in devices like Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, among others.
|
||||||
|
|
||||||
|
- **armv6 / arm / arm32**: Geared towards very old embedded devices, this architecture, while less prevalent, is still in use. Devices such as Raspberry Pi 1, Raspberry Pi Zero/Zero W, rely on this architecture.
|
||||||
|
|
||||||
|
- **armv5 / arm / arm32**: An older architecture primarily associated with early embedded systems, it is less common today but may still be found in legacy devices like early Raspberry Pi versions and some older smartphones.
|
||||||
|
</details>
|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
@@ -136,6 +212,8 @@ update to latest version
|
|||||||
- Russian
|
- Russian
|
||||||
- Vietnamese
|
- Vietnamese
|
||||||
- Spanish
|
- Spanish
|
||||||
|
- Indonesian
|
||||||
|
- Ukrainian
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -177,34 +255,6 @@ update to latest version
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## SSL Certificate
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click for SSL Certificate</summary>
|
|
||||||
|
|
||||||
### Cloudflare
|
|
||||||
|
|
||||||
The 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
|
|
||||||
|
|
||||||
**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
|
|
||||||
|
|
||||||
|
|
||||||
### Certbot
|
|
||||||
```
|
|
||||||
apt-get install certbot -y
|
|
||||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
|
||||||
certbot renew --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
|
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -257,13 +307,13 @@ If you want to use routing to WARP before v2.1.0 follow steps as below:
|
|||||||
2. Select `IP Limit Management`.
|
2. Select `IP Limit Management`.
|
||||||
3. Choose the appropriate options based on your needs.
|
3. Choose the appropriate options based on your needs.
|
||||||
|
|
||||||
- make sure you have access.log on your Xray Configuration
|
- make sure you have ./access.log on your Xray Configuration after v2.1.3 we have an option for it
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
"access": "./access.log",
|
||||||
"access": "./access.log",
|
"dnsLog": false,
|
||||||
"error": "./error.log"
|
"loglevel": "warning"
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -398,20 +448,6 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## Supported Architectures and Devices
|
|
||||||
|
|
||||||
Supports a variety of different architectures and devices. Here are some of the main architectures that we support:
|
|
||||||
|
|
||||||
- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems.
|
|
||||||
|
|
||||||
- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...)
|
|
||||||
|
|
||||||
- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...)
|
|
||||||
|
|
||||||
- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...)
|
|
||||||
|
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.1.1
|
2.2.5
|
||||||
@@ -21,6 +21,7 @@ var db *gorm.DB
|
|||||||
var initializers = []func() error{
|
var initializers = []func() error{
|
||||||
initUser,
|
initUser,
|
||||||
initInbound,
|
initInbound,
|
||||||
|
initOutbound,
|
||||||
initSetting,
|
initSetting,
|
||||||
initInboundClientIps,
|
initInboundClientIps,
|
||||||
initClientTraffic,
|
initClientTraffic,
|
||||||
@@ -51,6 +52,10 @@ func initInbound() error {
|
|||||||
return db.AutoMigrate(&model.Inbound{})
|
return db.AutoMigrate(&model.Inbound{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initOutbound() error {
|
||||||
|
return db.AutoMigrate(&model.OutboundTraffics{})
|
||||||
|
}
|
||||||
|
|
||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"x-ui/util/json_util"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
@@ -44,6 +45,15 @@ type Inbound struct {
|
|||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundTraffics struct {
|
||||||
|
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
Up int64 `json:"up" form:"up" gorm:"default:0"`
|
||||||
|
Down int64 `json:"down" form:"down" gorm:"default:0"`
|
||||||
|
Total int64 `json:"total" form:"total" gorm:"default:0"`
|
||||||
|
}
|
||||||
|
|
||||||
type InboundClientIps struct {
|
type InboundClientIps struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
||||||
|
|||||||
69
go.mod
69
go.mod
@@ -1,72 +1,72 @@
|
|||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.21.4
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Calidity/gin-sessions v1.3.1
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/mymmrac/telego v0.28.0
|
github.com/mymmrac/telego v0.29.1
|
||||||
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/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12
|
github.com/shirou/gopsutil/v3 v3.24.2
|
||||||
github.com/valyala/fasthttp v1.51.0
|
github.com/valyala/fasthttp v1.52.0
|
||||||
github.com/xtls/xray-core v1.8.7
|
github.com/xtls/xray-core v1.8.9
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/grpc v1.60.1
|
google.golang.org/grpc v1.62.1
|
||||||
gorm.io/driver/sqlite v1.5.4
|
gorm.io/driver/sqlite v1.5.5
|
||||||
gorm.io/gorm v1.25.5
|
gorm.io/gorm v1.25.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/bytedance/sonic v1.10.2 // indirect
|
github.com/bytedance/sonic v1.11.2 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||||
github.com/fasthttp/router v1.4.22 // indirect
|
github.com/fasthttp/router v1.5.0 // indirect
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.16.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/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.2.2 // indirect
|
github.com/gorilla/sessions v1.2.2 // indirect
|
||||||
github.com/gorilla/websocket v1.5.1 // indirect
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
|
github.com/grbit/go-json v0.11.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.19 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/quic-go v0.41.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.40.1 // indirect
|
github.com/refraction-networking/utls v1.6.3 // indirect
|
||||||
github.com/refraction-networking/utls v1.6.0 // indirect
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
github.com/sagernet/sing v0.3.0 // indirect
|
github.com/sagernet/sing v0.3.6 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
@@ -75,24 +75,25 @@ require (
|
|||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fastjson v1.6.4 // indirect
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.6.0 // indirect
|
golang.org/x/arch v0.6.0 // indirect
|
||||||
golang.org/x/crypto v0.18.0 // indirect
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/mod v0.16.0 // indirect
|
||||||
golang.org/x/net v0.20.0 // indirect
|
golang.org/x/net v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.16.1 // indirect
|
golang.org/x/tools v0.19.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
|
||||||
lukechampine.com/blake3 v1.2.1 // indirect
|
lukechampine.com/blake3 v1.2.1 // indirect
|
||||||
|
|||||||
157
go.sum
157
go.sum
@@ -12,16 +12,16 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8
|
|||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||||
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
|
||||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
@@ -41,8 +41,8 @@ github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fp
|
|||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/fasthttp/router v1.4.22 h1:qwWcYBbndVDwts4dKaz+A2ehsnbKilmiP6pUhXBfYKo=
|
github.com/fasthttp/router v1.5.0 h1:3Qbbo27HAPzwbpRzgiV5V9+2faPkPt3eNuRaDV6LYDA=
|
||||||
github.com/fasthttp/router v1.4.22/go.mod h1:KeMvHLqhlB9vyDWD5TSvTccl9qeWrjSSiTJrJALHKV0=
|
github.com/fasthttp/router v1.5.0/go.mod h1:FddcKNXFZg1imHcy+uKB0oo/o6yE9zD3wNguqlhWDak=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
@@ -61,8 +61,8 @@ 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/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/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-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.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
@@ -76,8 +76,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
|
|||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
|
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||||
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
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 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
@@ -88,13 +88,13 @@ 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/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.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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
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.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.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
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 v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
@@ -111,8 +111,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
|||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/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-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||||
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
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.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
@@ -124,6 +124,8 @@ github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTj
|
|||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
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 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
|
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
|
||||||
|
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
@@ -136,11 +138,11 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
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.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.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
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/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
@@ -153,8 +155,8 @@ 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
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-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
|
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/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||||
@@ -163,28 +165,28 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN
|
|||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
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 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/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.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
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-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mymmrac/telego v0.28.0 h1:DNXaYISeZw1J9oB81vCNdskLow8gCRRUJxufqLuH3XE=
|
github.com/mymmrac/telego v0.29.1 h1:nsNnK0mS18OL+unoDjDI6BVfafJBbT8Wtj7rCzEWoM8=
|
||||||
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg=
|
github.com/mymmrac/telego v0.29.1/go.mod h1:ZLD1+L2TQRr97NPOCoN1V2w8y9kmFov33OfZ3qT8cF4=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
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/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.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
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 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
@@ -206,12 +208,10 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
|||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
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/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/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
||||||
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.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
||||||
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
|
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
||||||
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
||||||
github.com/refraction-networking/utls v1.6.0 h1:X5vQMqVx7dY7ehxxqkFER/W6DSjy8TMqSItXm8hRDYQ=
|
|
||||||
github.com/refraction-networking/utls v1.6.0/go.mod h1:kHJ6R9DFFA0WsRgBM35iiDku4O7AqPR6y79iuzW7b10=
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
@@ -221,17 +221,17 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
|||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/sagernet/sing v0.3.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8=
|
github.com/sagernet/sing v0.3.6 h1:dsEdYLKBQlrxUfw1a92x0VdPvR1/BOxQ+HIMyaoEJsQ=
|
||||||
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
|
github.com/sagernet/sing v0.3.6/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
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/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
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/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
@@ -270,9 +270,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/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.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/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
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/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
@@ -288,8 +288,10 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
|||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
|
||||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
|
||||||
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
|
||||||
@@ -299,10 +301,10 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
|
|||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
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 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
|
||||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||||
github.com/xtls/xray-core v1.8.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA=
|
github.com/xtls/xray-core v1.8.9 h1:wefcON0behu4DoQvCKJYZKsJlSvNhyq2I7vC2fxLFcY=
|
||||||
github.com/xtls/xray-core v1.8.7/go.mod h1:9rFpflfQbgFeH1VKJw7yUmEy7myOyDCgNXXl0bmmyOo=
|
github.com/xtls/xray-core v1.8.9/go.mod h1:XDE4f422qJKAU3hNDSNZyWrOHvn9kF8UHVdyOzU38rc=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
@@ -319,16 +321,16 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
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-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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -338,8 +340,8 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
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-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-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@@ -369,9 +371,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.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.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -388,8 +390,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/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-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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
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-20191204190536-9bdfabe68543/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 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
@@ -407,19 +409,18 @@ 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-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-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 v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
|
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-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
|
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.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
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.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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||||
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
@@ -435,10 +436,10 @@ gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
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 h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
|
||||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||||
|
|||||||
40
install.sh
40
install.sh
@@ -26,12 +26,15 @@ echo "The OS release is: $release"
|
|||||||
arch3xui() {
|
arch3xui() {
|
||||||
case "$(uname -m)" in
|
case "$(uname -m)" in
|
||||||
x86_64 | x64 | amd64) echo 'amd64' ;;
|
x86_64 | x64 | amd64) echo 'amd64' ;;
|
||||||
|
i*86 | x86) echo '386' ;;
|
||||||
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
|
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
|
||||||
armv7* | armv7 | arm) echo 'armv7' ;;
|
armv7* | armv7 | arm) echo 'armv7' ;;
|
||||||
armv6* | armv6 | arm) echo 'armv6' ;;
|
armv6* | armv6) echo 'armv6' ;;
|
||||||
|
armv5* | armv5) echo 'armv5' ;;
|
||||||
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "arch: $(arch3xui)"
|
echo "arch: $(arch3xui)"
|
||||||
|
|
||||||
os_version=""
|
os_version=""
|
||||||
@@ -78,19 +81,21 @@ fi
|
|||||||
|
|
||||||
install_base() {
|
install_base() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
centos|fedora|almalinux|rocky)
|
centos | almalinux | rocky)
|
||||||
yum -y update && yum install -y -q wget curl tar
|
yum -y update && yum install -y -q wget curl tar tzdata
|
||||||
;;
|
;;
|
||||||
arch|manjaro)
|
fedora)
|
||||||
pacman -Syu && pacman -Syu --noconfirm wget curl tar
|
dnf -y update && dnf install -y -q wget curl tar tzdata
|
||||||
;;
|
;;
|
||||||
*)
|
arch | manjaro)
|
||||||
apt-get update && apt install -y -q wget curl tar
|
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
apt-get update && apt install -y -q wget curl tar tzdata
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# This function will be called when user installed x-ui out of security
|
# This function will be called when user installed x-ui out of security
|
||||||
config_after_install() {
|
config_after_install() {
|
||||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||||
@@ -118,9 +123,9 @@ config_after_install() {
|
|||||||
echo -e "${green}username:${usernameTemp}${plain}"
|
echo -e "${green}username:${usernameTemp}${plain}"
|
||||||
echo -e "${green}password:${passwordTemp}${plain}"
|
echo -e "${green}password:${passwordTemp}${plain}"
|
||||||
echo -e "###############################################"
|
echo -e "###############################################"
|
||||||
echo -e "${red}if you forgot your login info,you can type x-ui and then type 7 to check after installation${plain}"
|
echo -e "${red}if you forgot your login info,you can type x-ui and then type 8 to check after installation${plain}"
|
||||||
else
|
else
|
||||||
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}"
|
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 8 to check${plain}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
/usr/local/x-ui/x-ui migrate
|
/usr/local/x-ui/x-ui migrate
|
||||||
@@ -163,11 +168,11 @@ install_x-ui() {
|
|||||||
chmod +x x-ui
|
chmod +x x-ui
|
||||||
|
|
||||||
# Check the system's architecture and rename the file accordingly
|
# Check the system's architecture and rename the file accordingly
|
||||||
if [[ $(arch3xui) == "armv6" || $(arch3xui) == "armv7" ]]; then
|
if [[ $(arch3xui) == "armv5" || $(arch3xui) == "armv6" || $(arch3xui) == "armv7" ]]; then
|
||||||
mv bin/xray-linux-$(arch3xui) bin/xray-linux-arm
|
mv bin/xray-linux-$(arch3xui) bin/xray-linux-arm
|
||||||
chmod +x bin/xray-linux-arm
|
chmod +x bin/xray-linux-arm
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x x-ui bin/xray-linux-$(arch3xui)
|
chmod +x x-ui bin/xray-linux-$(arch3xui)
|
||||||
cp -f x-ui.service /etc/systemd/system/
|
cp -f x-ui.service /etc/systemd/system/
|
||||||
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
||||||
@@ -197,7 +202,6 @@ install_x-ui() {
|
|||||||
echo -e "----------------------------------------------"
|
echo -e "----------------------------------------------"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
echo -e "${green}Running...${plain}"
|
echo -e "${green}Running...${plain}"
|
||||||
install_base
|
install_base
|
||||||
install_x-ui $1
|
install_x-ui $1
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import (
|
|||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger *logging.Logger
|
var (
|
||||||
var logBuffer []struct {
|
logger *logging.Logger
|
||||||
time string
|
logBuffer []struct {
|
||||||
level logging.Level
|
time string
|
||||||
log string
|
level logging.Level
|
||||||
}
|
log string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
InitLogger(logging.INFO)
|
InitLogger(logging.INFO)
|
||||||
@@ -65,6 +67,16 @@ func Infof(format string, args ...interface{}) {
|
|||||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Notice(args ...interface{}) {
|
||||||
|
logger.Notice(args...)
|
||||||
|
addToBuffer("NOTICE", fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Noticef(format string, args ...interface{}) {
|
||||||
|
logger.Noticef(format, args...)
|
||||||
|
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...interface{}) {
|
||||||
logger.Warning(args...)
|
logger.Warning(args...)
|
||||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -8,6 +8,7 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
@@ -342,7 +343,7 @@ func main() {
|
|||||||
updateTgbotEnableSts(enabletgbot)
|
updateTgbotEnableSts(enabletgbot)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'setting' subcommands")
|
fmt.Println("Invalid subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
runCmd.Usage()
|
runCmd.Usage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|||||||
BIN
media/3X-UI.png
Normal file
BIN
media/3X-UI.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
87
sub/default.json
Normal file
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": "AsIs",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"network": "tcp,udp",
|
||||||
|
"outboundTag": "proxy"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stats": {}
|
||||||
|
}
|
||||||
71
sub/sub.go
71
sub/sub.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
@@ -47,11 +48,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
subPath, err := s.settingService.GetSubPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
subDomain, err := s.settingService.GetSubDomain()
|
subDomain, err := s.settingService.GetSubDomain()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -61,15 +57,50 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
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 = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
g := engine.Group("/")
|
||||||
|
|
||||||
|
s.sub = NewSUBController(g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment)
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() (err error) {
|
func (s *Server) Start() (err error) {
|
||||||
//This is an anonymous function, no function name
|
// This is an anonymous function, no function name
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Stop()
|
s.Stop()
|
||||||
@@ -114,21 +145,19 @@ func (s *Server) Start() (err error) {
|
|||||||
|
|
||||||
if certFile != "" || keyFile != "" {
|
if certFile != "" || keyFile != "" {
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
listener.Close()
|
c := &tls.Config{
|
||||||
return err
|
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 {
|
} else {
|
||||||
logger.Info("Sub server run http on", listener.Addr())
|
logger.Info("sub server run http on", listener.Addr())
|
||||||
}
|
}
|
||||||
s.listener = listener
|
s.listener = listener
|
||||||
|
|
||||||
|
|||||||
@@ -3,34 +3,57 @@ package sub
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"strings"
|
"strings"
|
||||||
"x-ui/web/service"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SUBController struct {
|
type SUBController struct {
|
||||||
subService SubService
|
subPath string
|
||||||
settingService service.SettingService
|
subJsonPath string
|
||||||
|
subEncrypt bool
|
||||||
|
updateInterval string
|
||||||
|
|
||||||
|
subService *SubService
|
||||||
|
subJsonService *SubJsonService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
func NewSUBController(
|
||||||
a := &SUBController{}
|
g *gin.RouterGroup,
|
||||||
|
subPath string,
|
||||||
|
jsonPath string,
|
||||||
|
encrypt bool,
|
||||||
|
showInfo bool,
|
||||||
|
rModel string,
|
||||||
|
update string,
|
||||||
|
jsonFragment string,
|
||||||
|
) *SUBController {
|
||||||
|
sub := NewSubService(showInfo, rModel)
|
||||||
|
a := &SUBController{
|
||||||
|
subPath: subPath,
|
||||||
|
subJsonPath: jsonPath,
|
||||||
|
subEncrypt: encrypt,
|
||||||
|
updateInterval: update,
|
||||||
|
|
||||||
|
subService: sub,
|
||||||
|
subJsonService: NewSubJsonService(jsonFragment, sub),
|
||||||
|
}
|
||||||
a.initRouter(g)
|
a.initRouter(g)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
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) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
|
||||||
subShowInfo, _ := a.settingService.GetSubShowInfo()
|
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
|
subs, header, err := a.subService.GetSubs(subId, host)
|
||||||
if err != nil || len(subs) == 0 {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
@@ -40,14 +63,31 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
c.Writer.Header().Set("Profile-Title", subId)
|
||||||
|
|
||||||
if subEncrypt {
|
if a.subEncrypt {
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
} else {
|
} else {
|
||||||
c.String(200, result)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
359
sub/subJsonService.go
Normal file
359
sub/subJsonService.go
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
inboundService service.InboundService
|
||||||
|
SubService *SubService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubJsonService(fragment 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 fragment != "" {
|
||||||
|
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SubJsonService{
|
||||||
|
configJson: configJson,
|
||||||
|
defaultOutbounds: defaultOutbounds,
|
||||||
|
fragment: fragment,
|
||||||
|
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
|
||||||
|
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"])
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
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 map[string]interface{} `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"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
"x-ui/util/random"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
@@ -25,47 +27,42 @@ type SubService struct {
|
|||||||
settingService service.SettingService
|
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.address = host
|
||||||
s.showInfo = showInfo
|
|
||||||
var result []string
|
var result []string
|
||||||
var headers []string
|
var header string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
var clientTraffics []xray.ClientTraffic
|
var clientTraffics []xray.ClientTraffic
|
||||||
inbounds, err := s.getInboundsBySubId(subId)
|
inbounds, err := s.getInboundsBySubId(subId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, "", err
|
||||||
}
|
|
||||||
s.remarkModel, err = s.settingService.GetRemarkModel()
|
|
||||||
if err != nil {
|
|
||||||
s.remarkModel = "-ieo"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.datepicker, err = s.settingService.GetDatepicker()
|
s.datepicker, err = s.settingService.GetDatepicker()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.datepicker = "gregorian"
|
s.datepicker = "gregorian"
|
||||||
}
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.GetClients(inbound)
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
logger.Error("SubService - GetClients: Unable to get clients from inbound")
|
||||||
}
|
}
|
||||||
if clients == nil {
|
if clients == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
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 {
|
if err == nil {
|
||||||
inbound.Listen = fallbackMaster.Listen
|
inbound.Listen = listen
|
||||||
inbound.Port = fallbackMaster.Port
|
inbound.Port = port
|
||||||
var stream map[string]interface{}
|
inbound.StreamSettings = streamSettings
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
@@ -76,6 +73,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare statistics
|
||||||
for index, clientTraffic := range clientTraffics {
|
for index, clientTraffic := range clientTraffics {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
traffic.Up = clientTraffic.Up
|
traffic.Up = clientTraffic.Up
|
||||||
@@ -97,11 +96,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))
|
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||||
updateInterval, _ := s.settingService.GetSubUpdates()
|
return result, header, nil
|
||||||
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
|
||||||
headers = append(headers, subId)
|
|
||||||
return result, headers, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
@@ -130,7 +126,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
|||||||
return xray.ClientTraffic{}
|
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()
|
db := database.GetDB()
|
||||||
var inbound *model.Inbound
|
var inbound *model.Inbound
|
||||||
err := db.Model(model.Inbound{}).
|
err := db.Model(model.Inbound{}).
|
||||||
@@ -138,9 +134,19 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
|||||||
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
||||||
Find(&inbound).Error
|
Find(&inbound).Error
|
||||||
if err != nil {
|
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 {
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
@@ -208,9 +214,14 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
obj["path"] = grpc["serviceName"].(string)
|
obj["path"] = grpc["serviceName"].(string)
|
||||||
|
obj["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
obj["type"] = "multi"
|
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)
|
security, _ := stream["security"].(string)
|
||||||
@@ -342,9 +353,14 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
params["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
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)
|
security, _ := stream["security"].(string)
|
||||||
@@ -387,25 +403,21 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
if realitySetting != nil {
|
if realitySetting != nil {
|
||||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||||
sNames, _ := sniValue.([]interface{})
|
sNames, _ := sniValue.([]interface{})
|
||||||
params["sni"], _ = sNames[0].(string)
|
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||||
}
|
}
|
||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]interface{})
|
shortIds, _ := sidValue.([]interface{})
|
||||||
params["sid"], _ = shortIds[0].(string)
|
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||||
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
params["spx"] = "/" + random.Seq(15)
|
||||||
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
|
|
||||||
params["spx"] = spx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -558,9 +570,14 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
params["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
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)
|
security, _ := stream["security"].(string)
|
||||||
@@ -578,6 +595,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
params["sni"], _ = sniValue.(string)
|
params["sni"], _ = sniValue.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
@@ -598,25 +616,21 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
if realitySetting != nil {
|
if realitySetting != nil {
|
||||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||||
sNames, _ := sniValue.([]interface{})
|
sNames, _ := sniValue.([]interface{})
|
||||||
params["sni"], _ = sNames[0].(string)
|
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||||
}
|
}
|
||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]interface{})
|
shortIds, _ := sidValue.([]interface{})
|
||||||
params["sid"], _ = shortIds[0].(string)
|
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||||
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
params["spx"] = "/" + random.Seq(15)
|
||||||
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
|
|
||||||
params["spx"] = spx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -774,9 +788,14 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
params["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
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)
|
security, _ := stream["security"].(string)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package common
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,18 @@ package random
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var numSeq [10]rune
|
var (
|
||||||
var lowerSeq [26]rune
|
numSeq [10]rune
|
||||||
var upperSeq [26]rune
|
lowerSeq [26]rune
|
||||||
var numLowerSeq [36]rune
|
upperSeq [26]rune
|
||||||
var numUpperSeq [36]rune
|
numLowerSeq [36]rune
|
||||||
var allSeq [62]rune
|
numUpperSeq [36]rune
|
||||||
|
allSeq [62]rune
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
numSeq[i] = rune('0' + i)
|
numSeq[i] = rune('0' + i)
|
||||||
}
|
}
|
||||||
@@ -41,3 +40,7 @@ func Seq(n int) string {
|
|||||||
}
|
}
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Num(n int) int {
|
||||||
|
return rand.Intn(n)
|
||||||
|
}
|
||||||
|
|||||||
@@ -538,7 +538,7 @@
|
|||||||
|
|
||||||
var on = function(emitter, type, f) {
|
var on = function(emitter, type, f) {
|
||||||
if (emitter.addEventListener) {
|
if (emitter.addEventListener) {
|
||||||
emitter.addEventListener(type, f, false);
|
emitter.addEventListener(type, f, { passive: false });
|
||||||
} else if (emitter.attachEvent) {
|
} else if (emitter.attachEvent) {
|
||||||
emitter.attachEvent("on" + type, f);
|
emitter.attachEvent("on" + type, f);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ THE SOFTWARE.
|
|||||||
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
|
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
|
||||||
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
|
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
|
||||||
|
|
||||||
.dark .cm-s-xq.CodeMirror { background-color: #222D42; border-color: #2c3950; color: rgb(255 255 255 / 65%); }
|
.dark .cm-s-xq.CodeMirror { background-color: var(--dark-color-surface-200); border-color: var(--dark-color-surface-300); color: rgb(255 255 255 / 65%); }
|
||||||
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 30%); border-color: #008771; transition: all .3s; }
|
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 30%); border-color: #008771; transition: all .3s; }
|
||||||
.dark .cm-s-xq div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); }
|
.dark .cm-s-xq div.CodeMirror-selected { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); }
|
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); }
|
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid #2c3950; }
|
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid var(--dark-color-surface-300); }
|
||||||
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
|
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
|
||||||
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
|
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
|
||||||
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
|
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
|
||||||
@@ -80,7 +80,7 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
.Line-Hover{transition: all .2s;}
|
.Line-Hover{transition: all .2s;}
|
||||||
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
|
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
|
||||||
.dark .Line-Hover:hover{ background-color: rgb(0 0 0 / 20%) !important; }
|
.dark .Line-Hover:hover{ background-color: var(--dark-color-codemirror-line-hover) !important; }
|
||||||
|
|
||||||
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
||||||
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
||||||
|
|||||||
@@ -1,3 +1,92 @@
|
|||||||
|
:root {
|
||||||
|
--color-primary-100: #008771;
|
||||||
|
--dark-color-background: #0a1222;
|
||||||
|
--dark-color-surface-100: #151f31;
|
||||||
|
--dark-color-surface-200: #222d42;
|
||||||
|
--dark-color-surface-300: #2c3950;
|
||||||
|
--dark-color-surface-400: rgba(65, 85, 119, 0.5); /* line */
|
||||||
|
--dark-color-surface-500: #2c3950; /* popover & switch btn */
|
||||||
|
--dark-color-surface-600: #313f5a; /* dropmenu hover */
|
||||||
|
--dark-color-surface-700: #111929; /* modals */
|
||||||
|
--dark-color-table-hover: rgba(44, 57, 80, 0.2);
|
||||||
|
--dark-color-text-primary: rgba(255, 255, 255, 0.75);
|
||||||
|
--dark-color-stroke: #2c3950;
|
||||||
|
--dark-color-btn-danger: #cd3838;
|
||||||
|
--dark-color-btn-danger-border: transparent;
|
||||||
|
--dark-color-btn-danger-hover: #e94b4b;
|
||||||
|
--dark-color-tag-bg: rgba(255, 255, 255, 0.05);
|
||||||
|
--dark-color-tag-border:rgba(255, 255, 255, 0.15);
|
||||||
|
--dark-color-tag-color:rgba(255, 255, 255, 0.75);
|
||||||
|
--dark-color-tag-green-bg: #112421;
|
||||||
|
--dark-color-tag-green-border: #195141;
|
||||||
|
--dark-color-tag-green-color: #3ad3ba;
|
||||||
|
--dark-color-tag-purple-bg: #201425;
|
||||||
|
--dark-color-tag-purple-border: #5a2969;
|
||||||
|
--dark-color-tag-purple-color: #d988cd;
|
||||||
|
--dark-color-tag-red-bg: #291515;
|
||||||
|
--dark-color-tag-red-border: #5c2626;
|
||||||
|
--dark-color-tag-red-color: #e04141;
|
||||||
|
--dark-color-tag-orange-bg: #312313;
|
||||||
|
--dark-color-tag-orange-border: #593914;
|
||||||
|
--dark-color-tag-orange-color: #ffa031;
|
||||||
|
--dark-color-tag-blue-bg: #111a2c;
|
||||||
|
--dark-color-tag-blue-border: #1348ab;
|
||||||
|
--dark-color-tag-blue-color: #529fff;
|
||||||
|
--dark-color-codemirror-line-hover: rgba(0, 135, 113, 0.2);
|
||||||
|
--dark-color-codemirror-line-selection: rgba(0, 135, 113, 0.3);
|
||||||
|
--dark-color-login-background: var(--dark-color-background);
|
||||||
|
--dark-color-login-wave: var(--dark-color-surface-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='ultra-dark'] {
|
||||||
|
--dark-color-background: #21242a;
|
||||||
|
--dark-color-surface-100: #0c0e12;
|
||||||
|
--dark-color-surface-200: #222327;
|
||||||
|
--dark-color-surface-300: #32353b;
|
||||||
|
--dark-color-surface-400: rgba(255, 255, 255, 0.1);
|
||||||
|
--dark-color-surface-500: #3b404b;
|
||||||
|
--dark-color-surface-600: #505663;
|
||||||
|
--dark-color-surface-700: #101113;
|
||||||
|
--dark-color-table-hover: rgba(89, 89, 89, 0.15);
|
||||||
|
--dark-color-text-primary: rgb(255 255 255 / 85%);
|
||||||
|
--dark-color-stroke: #202025;
|
||||||
|
--dark-color-tag-green-bg: #112421;
|
||||||
|
--dark-color-tag-green-border: #1d5f4d;
|
||||||
|
--dark-color-tag-green-color: #59cbac;
|
||||||
|
--dark-color-tag-purple-bg: #241121;
|
||||||
|
--dark-color-tag-purple-border: #5a2969;
|
||||||
|
--dark-color-tag-purple-color: #d686ca;
|
||||||
|
--dark-color-tag-red-bg: #2a1215;
|
||||||
|
--dark-color-tag-red-border: #58181c;
|
||||||
|
--dark-color-tag-red-color: #e84749;
|
||||||
|
--dark-color-tag-orange-bg: #2b1d11;
|
||||||
|
--dark-color-tag-orange-border: #593815;
|
||||||
|
--dark-color-tag-orange-color: #e89a3c;
|
||||||
|
--dark-color-tag-blue-bg: #111a2c;
|
||||||
|
--dark-color-tag-blue-border: #0f367e;
|
||||||
|
--dark-color-tag-blue-color: #3c89e8;
|
||||||
|
--dark-color-codemirror-line-hover: rgba(85, 85, 85, 0.3);
|
||||||
|
--dark-color-codemirror-line-selection: rgba(85, 85, 85, 0.4);
|
||||||
|
--dark-color-login-background: #0a2227;
|
||||||
|
--dark-color-login-wave: #0f2d32;
|
||||||
|
.ant-dropdown-menu-dark {
|
||||||
|
background-color: var(--dark-color-surface-500);
|
||||||
|
}
|
||||||
|
.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: var(--dark-color-surface-300);
|
||||||
|
}
|
||||||
|
.dark .waves-header {
|
||||||
|
background-color: #0a2227;
|
||||||
|
}
|
||||||
|
.dark .ant-calendar-year-panel-year:hover,
|
||||||
|
.dark .ant-calendar-month-panel-month:hover,
|
||||||
|
.dark .ant-calendar-decade-panel-decade:hover {
|
||||||
|
background-color: var(--dark-color-surface-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -16,7 +105,7 @@ body {
|
|||||||
font-feature-settings: "tnum";
|
font-feature-settings: "tnum";
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
--antd-wave-shadow-color: #008771;
|
--antd-wave-shadow-color: var(--color-primary-100);
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
text-size-adjust: 100%;
|
text-size-adjust: 100%;
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
@@ -26,7 +115,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: #cfe8e4;
|
background-color: #cfe8e4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +180,7 @@ style attribute {
|
|||||||
position: relative;
|
position: relative;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
.ant-table-body {
|
.ant-table-wrapper > div > div > div > div > div > div {
|
||||||
overflow-x: auto !important;
|
overflow-x: auto !important;
|
||||||
}
|
}
|
||||||
.ant-card-hoverable {
|
.ant-card-hoverable {
|
||||||
@@ -134,7 +223,7 @@ style attribute {
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
.ant-modal-body {
|
.ant-modal-body {
|
||||||
padding: 10px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
.ant-form-item-label {
|
.ant-form-item-label {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -218,7 +307,7 @@ style attribute {
|
|||||||
.ant-menu-submenu-active,
|
.ant-menu-submenu-active,
|
||||||
.ant-menu-submenu-title:hover,
|
.ant-menu-submenu-title:hover,
|
||||||
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open {
|
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: rgb(232 244 242);
|
background-color: rgb(232 244 242);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -480,14 +569,14 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.normal-icon:hover {
|
.normal-icon:hover {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DARK THEME */
|
/* DARK THEME */
|
||||||
|
|
||||||
.dark ::selection {
|
.dark ::selection {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .normal-icon:hover {
|
.dark .normal-icon:hover {
|
||||||
@@ -502,13 +591,14 @@ style attribute {
|
|||||||
.dark .ant-table,
|
.dark .ant-table,
|
||||||
.dark .ant-collapse-content,
|
.dark .ant-collapse-content,
|
||||||
.dark .ant-tabs {
|
.dark .ant-tabs {
|
||||||
background-color: #151f31;
|
background-color: var(--dark-color-surface-100);
|
||||||
color: #ffffffa6;
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-card-hoverable:hover,
|
.dark .ant-card-hoverable:hover,
|
||||||
.dark .ant-space-item > .ant-tabs:hover {
|
.dark .ant-space-item > .ant-tabs:hover {
|
||||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%);
|
/* box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%); */
|
||||||
|
box-shadow: 0 2px 8px transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark > .ant-layout,
|
.dark > .ant-layout,
|
||||||
@@ -518,8 +608,8 @@ style attribute {
|
|||||||
.dark .ant-table-expanded-row:hover,
|
.dark .ant-table-expanded-row:hover,
|
||||||
.dark .ant-table-expanded-row .ant-table-tbody,
|
.dark .ant-table-expanded-row .ant-table-tbody,
|
||||||
.dark .ant-calendar {
|
.dark .ant-calendar {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
color: rgb(255 255 255 /65%);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th {
|
.dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th {
|
||||||
@@ -528,7 +618,7 @@ style attribute {
|
|||||||
|
|
||||||
.dark .ant-calendar,
|
.dark .ant-calendar,
|
||||||
.dark .ant-card-bordered {
|
.dark .ant-card-bordered {
|
||||||
border-color: #151f31;
|
border-color: var(--dark-color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-bordered,
|
.dark .ant-table-bordered,
|
||||||
@@ -540,7 +630,7 @@ style attribute {
|
|||||||
.dark .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th,
|
.dark .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th,
|
||||||
.dark .ant-table-bordered .ant-table-tbody > tr > td,
|
.dark .ant-table-bordered .ant-table-tbody > tr > td,
|
||||||
.dark .ant-table-bordered .ant-table-thead > tr > th {
|
.dark .ant-table-bordered .ant-table-thead > tr > th {
|
||||||
border-color: #2c3950;
|
border-color: var(--dark-color-surface-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-tbody > tr > td,
|
.dark .ant-table-tbody > tr > td,
|
||||||
@@ -553,7 +643,7 @@ style attribute {
|
|||||||
.dark .ant-popover-title,
|
.dark .ant-popover-title,
|
||||||
.dark .ant-calendar-header,
|
.dark .ant-calendar-header,
|
||||||
.dark .ant-calendar-input-wrap {
|
.dark .ant-calendar-input-wrap {
|
||||||
border-bottom-color: #2c3950;
|
border-bottom-color: var(--dark-color-surface-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-modal-footer,
|
.dark .ant-modal-footer,
|
||||||
@@ -561,7 +651,7 @@ style attribute {
|
|||||||
.dark .ant-calendar-footer,
|
.dark .ant-calendar-footer,
|
||||||
.dark .ant-divider-horizontal.ant-divider-with-text-center:before,
|
.dark .ant-divider-horizontal.ant-divider-with-text-center:before,
|
||||||
.dark .ant-divider-horizontal.ant-divider-with-text-center:after {
|
.dark .ant-divider-horizontal.ant-divider-with-text-center:after {
|
||||||
border-top-color: #2c3950;
|
border-top-color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-progress-text,
|
.dark .ant-progress-text,
|
||||||
@@ -597,7 +687,7 @@ style attribute {
|
|||||||
.dark .ant-calendar-year-panel-year,
|
.dark .ant-calendar-year-panel-year,
|
||||||
.dark .ant-calendar-month-panel-month,
|
.dark .ant-calendar-month-panel-month,
|
||||||
.dark .ant-calendar-decade-panel-decade {
|
.dark .ant-calendar-decade-panel-decade {
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-list-item-meta-description {
|
.dark .ant-list-item-meta-description {
|
||||||
@@ -623,13 +713,12 @@ style attribute {
|
|||||||
.dark .ant-select-dropdown li,
|
.dark .ant-select-dropdown li,
|
||||||
.dark .ant-select-dropdown-menu-item,
|
.dark .ant-select-dropdown-menu-item,
|
||||||
.dark .ant-divider:not(.ant-divider-with-text-center),
|
.dark .ant-divider:not(.ant-divider-with-text-center),
|
||||||
.dark .ant-calendar-input,
|
|
||||||
.dark .client-table-header,
|
.dark .client-table-header,
|
||||||
.dark .ant-select-selection--multiple .ant-select-selection__choice,
|
.dark .ant-select-selection--multiple .ant-select-selection__choice,
|
||||||
.dark .ant-calendar-time-picker-inner {
|
.dark .ant-calendar-time-picker-inner {
|
||||||
background-color: #222d42;
|
background-color: var(--dark-color-surface-200);
|
||||||
border-color: #2c3950;
|
border-color: var(--dark-color-surface-300);
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-select-selection:hover,
|
.dark .ant-select-selection:hover,
|
||||||
@@ -639,34 +728,34 @@ style attribute {
|
|||||||
.dark .ant-input:hover,
|
.dark .ant-input:hover,
|
||||||
.dark .ant-input:focus {
|
.dark .ant-input:focus {
|
||||||
background-color: rgba(0, 135, 113, 0.3);
|
background-color: rgba(0, 135, 113, 0.3);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-text-primary);
|
||||||
background-color: rgb(10 117 87 / 30%);
|
background-color: rgb(10 117 87 / 30%);
|
||||||
border: 1px solid #008771;
|
border: 1px solid var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-radio-button-wrapper,
|
.dark .ant-radio-button-wrapper,
|
||||||
.dark .ant-radio-button-wrapper:before {
|
.dark .ant-radio-button-wrapper:before {
|
||||||
color: rgb(255 255 255 / 65%);
|
color: var(--dark-color-text-primary);
|
||||||
background-color: rgba(0, 135, 113, 0.3);
|
background-color: rgba(0, 135, 113, 0.3);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),
|
.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),
|
||||||
.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
|
.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: rgb(10 117 87 / 50%);
|
background-color: rgb(10 117 87 / 50%);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn-primary[disabled],
|
.dark .ant-btn-primary[disabled],
|
||||||
.dark .ant-btn-danger[disabled],
|
.dark .ant-btn-danger[disabled],
|
||||||
.dark .ant-calendar-ok-btn-disabled {
|
.dark .ant-calendar-ok-btn-disabled {
|
||||||
color: rgb(255 255 255 / 35%);
|
color: rgb(255 255 255 / 35%);
|
||||||
background-color: #2c3950;
|
background-color: var(--dark-color-surface-300);
|
||||||
border-color: #42516c;
|
border-color: #42516c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,7 +764,7 @@ style attribute {
|
|||||||
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
||||||
> td,
|
> td,
|
||||||
.dark .client-table-odd-row {
|
.dark .client-table-odd-row {
|
||||||
background-color: #00877122;
|
background-color: var(--dark-color-table-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-row-expand-icon {
|
.dark .ant-table-row-expand-icon {
|
||||||
@@ -685,38 +774,45 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-row-expand-icon:hover {
|
.dark .ant-table-row-expand-icon:hover {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: #fff0;
|
background-color: #fff0;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-switch:not(.ant-switch-checked),
|
.dark .ant-switch:not(.ant-switch-checked),
|
||||||
.dark .ant-progress-line .ant-progress-inner {
|
.dark .ant-progress-line .ant-progress-inner {
|
||||||
background-color: #2c3950;
|
background-color: var(--dark-color-surface-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-progress-circle-trail {
|
.dark .ant-progress-circle-trail {
|
||||||
stroke: #2c3950 !important;
|
stroke: var(--dark-color-stroke) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dropdown-menu-dark,
|
|
||||||
.dark .ant-popover-inner {
|
.dark .ant-popover-inner {
|
||||||
background-color: #222d42;
|
background-color: var(--dark-color-surface-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark > .ant-popover-content > .ant-popover-arrow {
|
.dark > .ant-popover-content > .ant-popover-arrow {
|
||||||
border-color: #222d42;
|
border-color: var(--dark-color-surface-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dark .ant-popover-inner {
|
||||||
|
background-color: var(--dark-color-surface-200);
|
||||||
|
}
|
||||||
|
.dark > .ant-popover-content > .ant-popover-arrow {
|
||||||
|
border-color: var(--dark-color-surface-200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,
|
.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,
|
||||||
.dark .ant-select-dropdown-menu-item-selected,
|
.dark .ant-select-dropdown-menu-item-selected,
|
||||||
.dark .ant-select-dropdown-menu-item:hover,
|
|
||||||
.dark .ant-calendar-time-picker-select-option-selected {
|
.dark .ant-calendar-time-picker-select-option-selected {
|
||||||
background-color: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-dark .ant-menu-item:hover {
|
.ant-menu-dark .ant-menu-item:hover {
|
||||||
background-color: #2c3950;
|
background-color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-alert-message {
|
.dark .ant-alert-message {
|
||||||
@@ -724,61 +820,61 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag {
|
.dark .ant-tag {
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-tag-color);
|
||||||
background-color: #ffffff0a;
|
background-color: var(--dark-color-tag-bg);
|
||||||
border-color: #344461;
|
border-color: var(--dark-color-tag-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-blue {
|
.dark .ant-tag-blue {
|
||||||
background-color: #111a2c;
|
background-color: var(--dark-color-tag-blue-bg);
|
||||||
border-color: #0f367e;
|
border-color: var(--dark-color-tag-blue-border);
|
||||||
color: #3c89e8;
|
color: var(--dark-color-tag-blue-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-red,
|
.dark .ant-tag-red,
|
||||||
.dark .ant-alert-error {
|
.dark .ant-alert-error {
|
||||||
background-color: #291515;
|
background-color: var(--dark-color-tag-red-bg);
|
||||||
border-color: #5c2626;
|
border-color: var(--dark-color-tag-red-border);
|
||||||
color: #e04141;
|
color: var(--dark-color-tag-red-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-orange,
|
.dark .ant-tag-orange,
|
||||||
.dark .ant-alert-warning {
|
.dark .ant-alert-warning {
|
||||||
background-color: #312313;
|
background-color: var(--dark-color-tag-orange-bg);
|
||||||
border-color: #593914;
|
border-color: var(--dark-color-tag-orange-border);
|
||||||
color: #ffa031;
|
color: var(--dark-color-tag-orange-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-green {
|
.dark .ant-tag-green {
|
||||||
background-color: #112421;
|
background-color: var(--dark-color-tag-green-bg);
|
||||||
border-color: #144840;
|
border-color: var(--dark-color-tag-green-border);
|
||||||
color: #33bca5;
|
color: var(--dark-color-tag-green-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-purple {
|
.dark .ant-tag-purple {
|
||||||
background-color: #2c1e32;
|
background-color: var(--dark-color-tag-purple-bg);
|
||||||
border-color: #49394e;
|
border-color: var(--dark-color-tag-purple-border);
|
||||||
color: #cfb9cc;
|
color: var(--dark-color-tag-purple-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-modal-content,
|
.dark .ant-modal-content,
|
||||||
.dark .ant-modal-header {
|
.dark .ant-modal-header {
|
||||||
background-color: #181f2c;
|
background-color: var(--dark-color-surface-700);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
||||||
.dark .ant-calendar-last-month-cell .ant-calendar-date {
|
.dark .ant-calendar-last-month-cell .ant-calendar-date {
|
||||||
color: #2c3950;
|
color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-selected-day .ant-calendar-date {
|
.dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
background-color: #008771 !important;
|
background-color: var(--color-primary-100) !important;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-date:hover,
|
.dark .ant-calendar-date:hover,
|
||||||
.dark .ant-calendar-time-picker-select li:hover {
|
.dark .ant-calendar-time-picker-select li:hover {
|
||||||
background-color: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,13 +888,15 @@ style attribute {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-time-picker-select {
|
.dark .ant-calendar-time-picker-select {
|
||||||
border-right-color: #2c3950;
|
border-right-color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-warning .ant-select-selection,
|
||||||
|
.has-warning .ant-select-selection:hover,
|
||||||
.has-warning .ant-input,
|
.has-warning .ant-input,
|
||||||
.has-warning .ant-input:hover {
|
.has-warning .ant-input:hover {
|
||||||
background-color: #ffeee1;
|
background-color: #ffeee1;
|
||||||
@@ -813,6 +911,8 @@ style attribute {
|
|||||||
border-color: #fec093;
|
border-color: #fec093;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .has-warning .ant-select-selection,
|
||||||
|
.dark .has-warning .ant-select-selection:hover,
|
||||||
.dark .has-warning .ant-input,
|
.dark .has-warning .ant-input,
|
||||||
.dark .has-warning .ant-input:hover {
|
.dark .has-warning .ant-input:hover {
|
||||||
border-color: #784e1d;
|
border-color: #784e1d;
|
||||||
@@ -828,7 +928,7 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .has-success .anticon {
|
.dark .has-success .anticon {
|
||||||
color: #61bf39;
|
color: var(--color-primary-100);
|
||||||
animation-name: diffZoomIn1 !important;
|
animation-name: diffZoomIn1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,19 +974,19 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-calendar-today .ant-calendar-date {
|
.ant-calendar-today .ant-calendar-date {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-today .ant-calendar-date {
|
.dark .ant-calendar-today .ant-calendar-date {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-calendar-selected-day .ant-calendar-date {
|
.ant-calendar-selected-day .ant-calendar-date {
|
||||||
background: #008771;
|
background: var(--color-primary-100);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -920,7 +1020,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
.ant-select-dropdown.ant-select-dropdown--multiple
|
.ant-select-dropdown.ant-select-dropdown--multiple
|
||||||
.ant-select-dropdown-menu-item-selected:hover
|
.ant-select-dropdown-menu-item-selected:hover
|
||||||
.ant-select-selected-icon {
|
.ant-select-selected-icon {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
.ant-select-selection:hover,
|
.ant-select-selection:hover,
|
||||||
.ant-input-number-focused,
|
.ant-input-number-focused,
|
||||||
@@ -929,7 +1029,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-input-number-handler:active {
|
.dark .ant-input-number-handler:active {
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,
|
.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,
|
||||||
@@ -957,7 +1057,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel-header {
|
.dark .ant-calendar-year-panel-header {
|
||||||
border-bottom: 1px solid #222d42;
|
border-bottom: 1px solid var(--dark-color-surface-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year,
|
.dark .ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year,
|
||||||
@@ -965,10 +1065,11 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
color: rgba(255, 255, 255, 0.35);
|
color: rgba(255, 255, 255, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark,
|
||||||
.dark .ant-calendar-year-panel-year:hover,
|
.dark .ant-calendar-year-panel-year:hover,
|
||||||
.dark .ant-calendar-month-panel-month:hover,
|
.dark .ant-calendar-month-panel-month:hover,
|
||||||
.dark .ant-calendar-decade-panel-decade:hover {
|
.dark .ant-calendar-decade-panel-decade:hover {
|
||||||
background-color: #222d42;
|
background-color: var(--dark-color-surface-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-header a:hover {
|
.dark .ant-calendar-header a:hover {
|
||||||
@@ -976,13 +1077,13 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-month-panel-header {
|
.dark .ant-calendar-month-panel-header {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
border-bottom: 1px solid #222d42;
|
border-bottom: 1px solid var(--dark-color-surface-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel,
|
.dark .ant-calendar-year-panel,
|
||||||
.dark .ant-calendar table {
|
.dark .ant-calendar table {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,
|
.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,
|
||||||
@@ -1000,7 +1101,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
.ant-calendar-decade-panel-selected-cell
|
.ant-calendar-decade-panel-selected-cell
|
||||||
.ant-calendar-decade-panel-decade:hover {
|
.ant-calendar-decade-panel-decade:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-last-month-cell .ant-calendar-date,
|
.dark .ant-calendar-last-month-cell .ant-calendar-date,
|
||||||
@@ -1014,8 +1115,8 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
|
|
||||||
.dark .ant-calendar-today .ant-calendar-date:hover {
|
.dark .ant-calendar-today .ant-calendar-date:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark
|
.dark
|
||||||
@@ -1028,8 +1129,8 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-decade-panel-header {
|
.dark .ant-calendar-decade-panel-header {
|
||||||
border-bottom: 1px solid #222d42;
|
border-bottom: 1px solid var(--dark-color-surface-200);
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-checkbox-inner {
|
.dark .ant-checkbox-inner {
|
||||||
@@ -1038,24 +1139,30 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-checkbox-checked .ant-checkbox-inner {
|
.dark .ant-checkbox-checked .ant-checkbox-inner {
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-input {
|
.dark .ant-calendar-input {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-input::placeholder {
|
.dark .ant-calendar-input::placeholder {
|
||||||
color: rgba(255, 255, 255, 0.25);
|
color: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(
|
||||||
|
:last-child
|
||||||
|
),
|
||||||
|
.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(
|
||||||
|
:last-child
|
||||||
|
),
|
||||||
|
.ant-input-group.ant-input-group-compact
|
||||||
|
> .ant-input:not(:first-child):not(:last-child),
|
||||||
|
.ant-input-number-handler,
|
||||||
.ant-input-number-handler-wrap {
|
.ant-input-number-handler-wrap {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input-number-handler {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-number {
|
.ant-input-number {
|
||||||
@@ -1089,15 +1196,15 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
> td,
|
> td,
|
||||||
.ant-table-thead
|
.ant-table-thead
|
||||||
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
||||||
> td {
|
> td,
|
||||||
|
.ant-calendar-time-picker-select li:hover {
|
||||||
background-color: rgb(232 244 242);
|
background-color: rgb(232 244 242);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-dropdown-menu-item:hover,
|
|
||||||
.dark .ant-dropdown-menu-submenu-title: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-active:not(.ant-select-dropdown-menu-item-disabled),
|
||||||
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
|
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
|
||||||
background-color: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-select-dropdown,
|
.ant-select-dropdown,
|
||||||
@@ -1110,6 +1217,8 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.qr-bg {
|
.qr-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1118,6 +1227,52 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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) {
|
.ant-input-group-addon:not(:first-child):not(:last-child) {
|
||||||
border-radius: 0rem 1rem 1rem 0rem;
|
border-radius: 0rem 1rem 1rem 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-tag {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b, strong {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-collapse>.ant-collapse-item>.ant-collapse-header {
|
||||||
|
padding: 10px 16px 10px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-message-notice-content {
|
||||||
|
background-color: var(--dark-color-surface-200);
|
||||||
|
border: 1px solid var(--dark-color-surface-300);
|
||||||
|
color: var(--dark-color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-danger {
|
||||||
|
background-color: var(--dark-color-btn-danger);
|
||||||
|
border-color: var(--dark-color-btn-danger-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-danger:focus, .ant-btn-danger:hover {
|
||||||
|
background-color: var(--dark-color-btn-danger-hover);
|
||||||
|
border-color: var(--dark-color-btn-danger-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-alert-close-icon .anticon-close:hover {
|
||||||
|
color: rgb(255 255 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-empty-small {
|
||||||
|
margin: 4px 0;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-empty-small .ant-empty-image {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-theme-switch:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -14,3 +14,17 @@ axios.interceptors.request.use(
|
|||||||
},
|
},
|
||||||
(error) => Promise.reject(error),
|
(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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ const supportLangs = [
|
|||||||
value: 'es-ES',
|
value: 'es-ES',
|
||||||
icon: '🇪🇸',
|
icon: '🇪🇸',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Indonesian',
|
||||||
|
value: 'id-ID',
|
||||||
|
icon: '🇮🇩',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Український',
|
||||||
|
value: 'uk-UA',
|
||||||
|
icon: '🇺🇦',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLang() {
|
function getLang() {
|
||||||
|
|||||||
@@ -51,7 +51,14 @@ const OutboundDomainStrategies = [
|
|||||||
"AsIs",
|
"AsIs",
|
||||||
"UseIP",
|
"UseIP",
|
||||||
"UseIPv4",
|
"UseIPv4",
|
||||||
"UseIPv6"
|
"UseIPv6",
|
||||||
|
"UseIPv6v4",
|
||||||
|
"UseIPv4v6",
|
||||||
|
"ForceIP",
|
||||||
|
"ForceIPv6v4",
|
||||||
|
"ForceIPv6",
|
||||||
|
"ForceIPv4v6",
|
||||||
|
"ForceIPv4"
|
||||||
];
|
];
|
||||||
|
|
||||||
const WireguardDomainStrategy = [
|
const WireguardDomainStrategy = [
|
||||||
@@ -268,6 +275,28 @@ class GrpcStreamSettings extends CommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
class TlsStreamSettings extends CommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
alpn=[],
|
alpn=[],
|
||||||
@@ -327,6 +356,34 @@ class RealityStreamSettings extends CommonClass {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
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 {
|
class StreamSettings extends CommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
@@ -339,6 +396,8 @@ class StreamSettings extends CommonClass {
|
|||||||
httpSettings=new HttpStreamSettings(),
|
httpSettings=new HttpStreamSettings(),
|
||||||
quicSettings=new QuicStreamSettings(),
|
quicSettings=new QuicStreamSettings(),
|
||||||
grpcSettings=new GrpcStreamSettings(),
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
httpupgradeSettings=new HttpUpgradeStreamSettings(),
|
||||||
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.network = network;
|
this.network = network;
|
||||||
@@ -351,6 +410,8 @@ class StreamSettings extends CommonClass {
|
|||||||
this.http = httpSettings;
|
this.http = httpSettings;
|
||||||
this.quic = quicSettings;
|
this.quic = quicSettings;
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
|
this.httpupgrade = httpupgradeSettings;
|
||||||
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTls() {
|
get isTls() {
|
||||||
@@ -361,6 +422,14 @@ class StreamSettings extends CommonClass {
|
|||||||
return this.security === "reality";
|
return this.security === "reality";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get sockoptSwitch() {
|
||||||
|
return this.sockopt != undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
set sockoptSwitch(value) {
|
||||||
|
this.sockopt = value ? new SockoptStreamSettings() : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
json.network,
|
json.network,
|
||||||
@@ -373,6 +442,8 @@ class StreamSettings extends CommonClass {
|
|||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +460,37 @@ class StreamSettings extends CommonClass {
|
|||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,12 +501,14 @@ class Outbound extends CommonClass {
|
|||||||
protocol=Protocols.VMess,
|
protocol=Protocols.VMess,
|
||||||
settings=null,
|
settings=null,
|
||||||
streamSettings = new StreamSettings(),
|
streamSettings = new StreamSettings(),
|
||||||
|
mux = new Mux(),
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
this._protocol = protocol;
|
this._protocol = protocol;
|
||||||
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
|
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
|
||||||
this.stream = streamSettings;
|
this.stream = streamSettings;
|
||||||
|
this.mux = mux;
|
||||||
}
|
}
|
||||||
|
|
||||||
get protocol() {
|
get protocol() {
|
||||||
@@ -418,8 +522,8 @@ class Outbound extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
||||||
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
|
return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.stream.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is used for xtls-rprx-vision
|
//this is used for xtls-rprx-vision
|
||||||
@@ -469,6 +573,7 @@ class Outbound extends CommonClass {
|
|||||||
json.protocol,
|
json.protocol,
|
||||||
Outbound.Settings.fromJson(json.protocol, json.settings),
|
Outbound.Settings.fromJson(json.protocol, json.settings),
|
||||||
StreamSettings.fromJson(json.streamSettings),
|
StreamSettings.fromJson(json.streamSettings),
|
||||||
|
Mux.fromJson(json.mux),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,6 +583,7 @@ class Outbound extends CommonClass {
|
|||||||
protocol: this.protocol,
|
protocol: this.protocol,
|
||||||
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
|
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
|
||||||
streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
|
streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
|
||||||
|
mux: this.mux?.enabled ? this.mux : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,6 +629,8 @@ class Outbound extends CommonClass {
|
|||||||
json.type ? json.type : 'none');
|
json.type ? json.type : 'none');
|
||||||
} else if (network === 'grpc') {
|
} else if (network === 'grpc') {
|
||||||
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
|
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
|
||||||
|
} else if (network === 'httpupgrade') {
|
||||||
|
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path,json.host);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(json.tls && json.tls == 'tls'){
|
if(json.tls && json.tls == 'tls'){
|
||||||
@@ -533,7 +641,6 @@ class Outbound extends CommonClass {
|
|||||||
json.allowInsecure);
|
json.allowInsecure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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, json.port, json.id), stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,6 +671,8 @@ class Outbound extends CommonClass {
|
|||||||
headerType ?? 'none');
|
headerType ?? 'none');
|
||||||
} else if (type === 'grpc') {
|
} 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('mode') == 'multi');
|
||||||
|
} else if (type === 'httpupgrade') {
|
||||||
|
stream.httpupgrade = new HttpUpgradeStreamSettings(path,host);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(security == 'tls'){
|
if(security == 'tls'){
|
||||||
@@ -580,7 +689,7 @@ class Outbound extends CommonClass {
|
|||||||
let sni=url.searchParams.get('sni') ?? '';
|
let sni=url.searchParams.get('sni') ?? '';
|
||||||
let sid=url.searchParams.get('sid') ?? '';
|
let sid=url.searchParams.get('sid') ?? '';
|
||||||
let spx=url.searchParams.get('spx') ?? '';
|
let spx=url.searchParams.get('spx') ?? '';
|
||||||
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
|
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = link.split('?');
|
let data = link.split('?');
|
||||||
@@ -861,13 +970,13 @@ Outbound.SocksSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
servers = json.servers;
|
let servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.SocksSettings(
|
return new Outbound.SocksSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,13 +1000,13 @@ Outbound.HttpSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
servers = json.servers;
|
let servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.HttpSettings(
|
return new Outbound.HttpSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,8 +1023,8 @@ Outbound.HttpSettings = class extends CommonClass {
|
|||||||
|
|
||||||
Outbound.WireguardSettings = class extends CommonClass {
|
Outbound.WireguardSettings = class extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
mtu=1420, secretKey=Wireguard.generateKeypair().privateKey,
|
mtu=1420, secretKey='',
|
||||||
address='', workers=2, domainStrategy='', reserved='',
|
address=[''], workers=2, domainStrategy='', reserved='',
|
||||||
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
||||||
super();
|
super();
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
@@ -957,7 +1066,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
|||||||
address: this.address ? this.address.split(",") : [],
|
address: this.address ? this.address.split(",") : [],
|
||||||
workers: this.workers?? undefined,
|
workers: this.workers?? undefined,
|
||||||
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
||||||
reserved: this.reserved ? this.reserved.split(",") : undefined,
|
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
|
||||||
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||||
kernelMode: this.kernelMode,
|
kernelMode: this.kernelMode,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class AllSetting {
|
|||||||
this.subListen = "";
|
this.subListen = "";
|
||||||
this.subPort = "2096";
|
this.subPort = "2096";
|
||||||
this.subPath = "/sub/";
|
this.subPath = "/sub/";
|
||||||
|
this.subJsonPath = "/json/";
|
||||||
this.subDomain = "";
|
this.subDomain = "";
|
||||||
this.subCertFile = "";
|
this.subCertFile = "";
|
||||||
this.subKeyFile = "";
|
this.subKeyFile = "";
|
||||||
@@ -35,6 +36,8 @@ class AllSetting {
|
|||||||
this.subEncrypt = true;
|
this.subEncrypt = true;
|
||||||
this.subShowInfo = false;
|
this.subShowInfo = false;
|
||||||
this.subURI = '';
|
this.subURI = '';
|
||||||
|
this.subJsonURI = '';
|
||||||
|
this.subJsonFragment = '';
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|||||||
@@ -446,16 +446,19 @@ class QuicStreamSettings extends XrayCommonClass {
|
|||||||
class GrpcStreamSettings extends XrayCommonClass {
|
class GrpcStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
serviceName="",
|
serviceName="",
|
||||||
|
authority="",
|
||||||
multiMode=false,
|
multiMode=false,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
|
this.authority = authority;
|
||||||
this.multiMode = multiMode;
|
this.multiMode = multiMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new GrpcStreamSettings(
|
return new GrpcStreamSettings(
|
||||||
json.serviceName,
|
json.serviceName,
|
||||||
|
json.authority,
|
||||||
json.multiMode
|
json.multiMode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -463,11 +466,36 @@ class GrpcStreamSettings extends XrayCommonClass {
|
|||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serviceName: this.serviceName,
|
serviceName: this.serviceName,
|
||||||
|
authority: this.authority,
|
||||||
multiMode: this.multiMode,
|
multiMode: this.multiMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HttpUpgradeStreamSettings extends XrayCommonClass {
|
||||||
|
constructor(acceptProxyProtocol=false, path='/', host='') {
|
||||||
|
super();
|
||||||
|
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||||
|
this.path = path;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new HttpUpgradeStreamSettings(
|
||||||
|
json.acceptProxyProtocol,
|
||||||
|
json.path,
|
||||||
|
json.host,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||||
|
path: this.path,
|
||||||
|
host: this.host,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TlsStreamSettings extends XrayCommonClass {
|
class TlsStreamSettings extends XrayCommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
@@ -833,6 +861,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings=new HttpStreamSettings(),
|
httpSettings=new HttpStreamSettings(),
|
||||||
quicSettings=new QuicStreamSettings(),
|
quicSettings=new QuicStreamSettings(),
|
||||||
grpcSettings=new GrpcStreamSettings(),
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
httpupgradeSettings=new HttpUpgradeStreamSettings(),
|
||||||
sockopt = undefined,
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -848,6 +877,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
this.http = httpSettings;
|
this.http = httpSettings;
|
||||||
this.quic = quicSettings;
|
this.quic = quicSettings;
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
|
this.httpupgrade = httpupgradeSettings;
|
||||||
this.sockopt = sockopt;
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,6 +940,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
SockoptStreamSettings.fromJson(json.sockopt),
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -929,6 +960,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
|
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1045,6 +1077,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.network === "http";
|
return this.network === "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isHttpupgrade() {
|
||||||
|
return this.network === "httpupgrade";
|
||||||
|
}
|
||||||
|
|
||||||
// Shadowsocks
|
// Shadowsocks
|
||||||
get method() {
|
get method() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
@@ -1075,6 +1111,8 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.stream.ws.getHeader("Host");
|
return this.stream.ws.getHeader("Host");
|
||||||
} else if (this.isH2) {
|
} else if (this.isH2) {
|
||||||
return this.stream.http.host[0];
|
return this.stream.http.host[0];
|
||||||
|
} else if (this.isHttpupgrade) {
|
||||||
|
return this.stream.httpupgrade.host;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1086,6 +1124,8 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.stream.ws.path;
|
return this.stream.ws.path;
|
||||||
} else if (this.isH2) {
|
} else if (this.isH2) {
|
||||||
return this.stream.http.path;
|
return this.stream.http.path;
|
||||||
|
} else if (this.isHttpupgrade) {
|
||||||
|
return this.stream.httpupgrade.path;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1121,7 +1161,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
|
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
|
//this is used for xtls-rprx-vision
|
||||||
@@ -1146,10 +1186,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
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() {
|
reset() {
|
||||||
this.port = RandomUtil.randomIntRange(10000, 60000);
|
this.port = RandomUtil.randomIntRange(10000, 60000);
|
||||||
this.listen = '';
|
this.listen = '';
|
||||||
@@ -1208,9 +1244,14 @@ class Inbound extends XrayCommonClass {
|
|||||||
obj.path = this.stream.quic.key;
|
obj.path = this.stream.quic.key;
|
||||||
} else if (network === 'grpc') {
|
} else if (network === 'grpc') {
|
||||||
obj.path = this.stream.grpc.serviceName;
|
obj.path = this.stream.grpc.serviceName;
|
||||||
|
obj.authority = this.stream.grpc.authority;
|
||||||
if (this.stream.grpc.multiMode){
|
if (this.stream.grpc.multiMode){
|
||||||
obj.type = 'multi'
|
obj.type = 'multi'
|
||||||
}
|
}
|
||||||
|
} else if (network === 'httpupgrade') {
|
||||||
|
let httpupgrade = this.stream.httpupgrade;
|
||||||
|
obj.path = httpupgrade.path;
|
||||||
|
obj.host = httpupgrade.host;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
@@ -1279,10 +1320,16 @@ class Inbound extends XrayCommonClass {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
params.set("authority", grpc.authority);
|
||||||
if(grpc.multiMode){
|
if(grpc.multiMode){
|
||||||
params.set("mode", "multi");
|
params.set("mode", "multi");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "httpupgrade":
|
||||||
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
|
params.set("path", httpupgrade.path);
|
||||||
|
params.set("host", httpupgrade.host);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
@@ -1393,10 +1440,16 @@ class Inbound extends XrayCommonClass {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
params.set("authority", grpc.authority);
|
||||||
if(grpc.multiMode){
|
if(grpc.multiMode){
|
||||||
params.set("mode", "multi");
|
params.set("mode", "multi");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "httpupgrade":
|
||||||
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
|
params.set("path", httpupgrade.path);
|
||||||
|
params.set("host", httpupgrade.host);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
@@ -1474,10 +1527,16 @@ class Inbound extends XrayCommonClass {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
params.set("authority", grpc.authority);
|
||||||
if(grpc.multiMode){
|
if(grpc.multiMode){
|
||||||
params.set("mode", "multi");
|
params.set("mode", "multi");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "httpupgrade":
|
||||||
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
|
params.set("path", httpupgrade.path);
|
||||||
|
params.set("host", httpupgrade.host);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
@@ -1534,6 +1593,28 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
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, 1.0.0.1\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) {
|
genLink(address='', port=this.port, forceTls='same', remark='', client) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
@@ -1557,7 +1638,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
const orderChars = remarkModel.slice(1);
|
const orderChars = remarkModel.slice(1);
|
||||||
let orders = {
|
let orders = {
|
||||||
'i': remark,
|
'i': remark,
|
||||||
'e': client ? client.email : '',
|
'e': email,
|
||||||
'o': '',
|
'o': '',
|
||||||
};
|
};
|
||||||
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
||||||
@@ -1580,6 +1661,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
||||||
|
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
||||||
if(this.clients){
|
if(this.clients){
|
||||||
let links = [];
|
let links = [];
|
||||||
this.clients.forEach((client) => {
|
this.clients.forEach((client) => {
|
||||||
@@ -1589,7 +1671,14 @@ class Inbound extends XrayCommonClass {
|
|||||||
});
|
});
|
||||||
return links.join('\r\n');
|
return links.join('\r\n');
|
||||||
} else {
|
} 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 '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2269,7 +2358,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addPeer() {
|
addPeer() {
|
||||||
this.peers.push(new Inbound.WireguardSettings.Peer());
|
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
delPeer(index) {
|
delPeer(index) {
|
||||||
@@ -2297,16 +2386,24 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||||
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) {
|
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
|
||||||
super();
|
super();
|
||||||
|
this.privateKey = privateKey
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
|
if (!this.publicKey){
|
||||||
|
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
||||||
|
}
|
||||||
this.psk = psk;
|
this.psk = psk;
|
||||||
|
allowedIPs.forEach((a,index) => {
|
||||||
|
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
|
||||||
|
})
|
||||||
this.allowedIPs = allowedIPs;
|
this.allowedIPs = allowedIPs;
|
||||||
this.keepAlive = keepAlive;
|
this.keepAlive = keepAlive;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}){
|
static fromJson(json={}){
|
||||||
return new Inbound.WireguardSettings.Peer(
|
return new Inbound.WireguardSettings.Peer(
|
||||||
|
json.privateKey,
|
||||||
json.publicKey,
|
json.publicKey,
|
||||||
json.preSharedKey,
|
json.preSharedKey,
|
||||||
json.allowedIPs,
|
json.allowedIPs,
|
||||||
@@ -2315,7 +2412,11 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
|
this.allowedIPs.forEach((a,index) => {
|
||||||
|
if (a.length>0 && !a.includes('/')) this.allowedIPs[index] += '/32';
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
|
privateKey: this.privateKey,
|
||||||
publicKey: this.publicKey,
|
publicKey: this.publicKey,
|
||||||
preSharedKey: this.psk.length>0 ? this.psk : undefined,
|
preSharedKey: this.psk.length>0 ? this.psk : undefined,
|
||||||
allowedIPs: this.allowedIPs,
|
allowedIPs: this.allowedIPs,
|
||||||
|
|||||||
@@ -131,11 +131,11 @@ class RandomUtil {
|
|||||||
static randomUUID() {
|
static randomUUID() {
|
||||||
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
|
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
|
||||||
return template.replace(/[xy]/g, function (c) {
|
return template.replace(/[xy]/g, function (c) {
|
||||||
const randomValues = new Uint8Array(1);
|
const randomValues = new Uint8Array(1);
|
||||||
crypto.getRandomValues(randomValues);
|
crypto.getRandomValues(randomValues);
|
||||||
let randomValue = randomValues[0] % 16;
|
let randomValue = randomValues[0] % 16;
|
||||||
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
|
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
|
||||||
return calculatedValue.toString(16);
|
return calculatedValue.toString(16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -22,91 +22,37 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
g = g.Group("/panel/api/inbounds")
|
g = g.Group("/panel/api/inbounds")
|
||||||
g.Use(a.checkLogin)
|
g.Use(a.checkLogin)
|
||||||
|
|
||||||
g.GET("/list", a.getAllInbounds)
|
|
||||||
g.GET("/get/:id", a.getSingleInbound)
|
|
||||||
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("/clientIps/:email", a.getClientIps)
|
|
||||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
|
||||||
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)
|
a.inboundController = NewInboundController(g)
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) getAllInbounds(c *gin.Context) {
|
inboundRoutes := []struct {
|
||||||
a.inboundController.getInbounds(c)
|
Method string
|
||||||
}
|
Path string
|
||||||
|
Handler gin.HandlerFunc
|
||||||
|
}{
|
||||||
|
{"GET", "/createbackup", a.createBackup},
|
||||||
|
{"GET", "/list", 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", "/clientIps/:email", a.inboundController.getClientIps},
|
||||||
|
{"POST", "/clearClientIps/:email", a.inboundController.clearClientIps},
|
||||||
|
{"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) getSingleInbound(c *gin.Context) {
|
for _, route := range inboundRoutes {
|
||||||
a.inboundController.getInbound(c)
|
g.Handle(route.Method, route.Path, route.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) getClientIps(c *gin.Context) {
|
|
||||||
a.inboundController.getClientIps(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) clearClientIps(c *gin.Context) {
|
|
||||||
a.inboundController.clearClientIps(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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) createBackup(c *gin.Context) {
|
func (a *APIController) createBackup(c *gin.Context) {
|
||||||
a.Tgbot.SendBackupToAdmins()
|
a.Tgbot.SendBackupToAdmins()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) onlines(c *gin.Context) {
|
|
||||||
a.inboundController.onlines(c)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/locale"
|
"x-ui/web/locale"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
@@ -9,13 +10,12 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseController struct {
|
type BaseController struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (a *BaseController) checkLogin(c *gin.Context) {
|
func (a *BaseController) checkLogin(c *gin.Context) {
|
||||||
if !session.IsLogin(c) {
|
if !session.IsLogin(c) {
|
||||||
if isAjax(c) {
|
if isAjax(c) {
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain"))
|
pureJsonMsg(c, http.StatusUnauthorized, false, I18nWeb(c, "pages.login.loginAgain"))
|
||||||
} else {
|
} else {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
@@ -86,7 +87,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
inbound.UserId = user.Id
|
inbound.UserId = user.Id
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||||
inbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
} else {
|
} else {
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||||
}
|
}
|
||||||
@@ -174,7 +175,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client(s) added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +196,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client deleted", nil)
|
jsonMsg(c, "Client deleted", nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,7 +219,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client updated", nil)
|
jsonMsg(c, "Client updated", nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +240,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "traffic reseted", nil)
|
jsonMsg(c, "traffic reseted", nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +284,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
|
|||||||
inbound.Id = 0
|
inbound.Id = 0
|
||||||
inbound.UserId = user.Id
|
inbound.UserId = user.Id
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||||
inbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
} else {
|
} else {
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
@@ -49,15 +50,15 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
var form LoginForm
|
var form LoginForm
|
||||||
err := c.ShouldBind(&form)
|
err := c.ShouldBind(&form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Username == "" {
|
if form.Username == "" {
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Password == "" {
|
if form.Password == "" {
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
if user == nil {
|
if user == nil {
|
||||||
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
|
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
@@ -48,18 +49,11 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
|||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusOK, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pureJsonMsg(c *gin.Context, success bool, msg string) {
|
func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) {
|
||||||
if success {
|
c.JSON(statusCode, entity.Msg{
|
||||||
c.JSON(http.StatusOK, entity.Msg{
|
Success: success,
|
||||||
Success: true,
|
Msg: msg,
|
||||||
Msg: msg,
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, entity.Msg{
|
|
||||||
Success: false,
|
|
||||||
Msg: msg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func html(c *gin.Context, name string, title string, data gin.H) {
|
func html(c *gin.Context, name string, title string, data gin.H) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type XraySettingController struct {
|
|||||||
XraySettingService service.XraySettingService
|
XraySettingService service.XraySettingService
|
||||||
SettingService service.SettingService
|
SettingService service.SettingService
|
||||||
InboundService service.InboundService
|
InboundService service.InboundService
|
||||||
|
OutboundService service.OutboundService
|
||||||
XrayService service.XrayService
|
XrayService service.XrayService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.GET("/getXrayResult", a.getXrayResult)
|
g.GET("/getXrayResult", a.getXrayResult)
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
g.POST("/warp/:action", a.warp)
|
g.POST("/warp/:action", a.warp)
|
||||||
|
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
|
||||||
|
g.POST("/resetOutboundsTraffic", a.resetOutboundsTraffic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
@@ -78,9 +81,27 @@ func (a *XraySettingController) warp(c *gin.Context) {
|
|||||||
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
||||||
case "license":
|
case "license":
|
||||||
license := c.PostForm("license")
|
license := c.PostForm("license")
|
||||||
println(license)
|
|
||||||
resp, err = a.XraySettingService.SetWarpLicence(license)
|
resp, err = a.XraySettingService.SetWarpLicence(license)
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonObj(c, resp, err)
|
jsonObj(c, resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
|
||||||
|
outboundsTraffic, err := a.OutboundService.GetOutboundsTraffic()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Error getting traffics", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, outboundsTraffic, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) resetOutboundsTraffic(c *gin.Context) {
|
||||||
|
tag := c.PostForm("tag")
|
||||||
|
err := a.OutboundService.ResetOutboundTraffic(tag)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Error in reset outbound traffics", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, "", nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,6 +49,9 @@ type AllSetting struct {
|
|||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
SubURI string `json:"subURI" form:"subURI"`
|
SubURI string `json:"subURI" form:"subURI"`
|
||||||
|
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
||||||
|
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
||||||
|
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
||||||
Datepicker string `json:"datepicker" form:"datepicker"`
|
Datepicker string `json:"datepicker" form:"datepicker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +109,13 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
s.SubPath += "/"
|
s.SubPath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(s.SubJsonPath, "/") {
|
||||||
|
s.SubJsonPath = "/" + s.SubJsonPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(s.SubJsonPath, "/") {
|
||||||
|
s.SubJsonPath += "/"
|
||||||
|
}
|
||||||
|
|
||||||
_, err := time.LoadLocation(s.TimeLocation)
|
_, err := time.LoadLocation(s.TimeLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError("time location not exist:", s.TimeLocation)
|
return common.NewError("time location not exist:", s.TimeLocation)
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import (
|
|||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var webServer WebServer
|
var (
|
||||||
var subServer SubServer
|
webServer WebServer
|
||||||
|
subServer SubServer
|
||||||
|
)
|
||||||
|
|
||||||
type WebServer interface {
|
type WebServer interface {
|
||||||
GetCron() *cron.Cron
|
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/ant-design-vue@1.7.8/antd.min.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
<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>
|
<style>
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -30,4 +28,5 @@
|
|||||||
</style>
|
</style>
|
||||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
|
<div id="message"></div>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{{define "promptModal"}}
|
{{define "promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
|
:confirm-loading="promptModal.confirmLoading"
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
value: '',
|
value: '',
|
||||||
okText: '{{ i18n "sure"}}',
|
okText: '{{ i18n "sure"}}',
|
||||||
visible: false,
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
keyEnter(e) {
|
keyEnter(e) {
|
||||||
if (this.type !== 'textarea') {
|
if (this.type !== 'textarea') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -30,7 +32,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ok() {
|
ok() {
|
||||||
promptModal.close();
|
|
||||||
promptModal.confirm(promptModal.value);
|
promptModal.confirm(promptModal.value);
|
||||||
},
|
},
|
||||||
confirm() {},
|
confirm() {},
|
||||||
@@ -53,7 +54,10 @@
|
|||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
}
|
},
|
||||||
|
loading(loading=true) {
|
||||||
|
this.confirmLoading = loading;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const promptModalApp = new Vue({
|
const promptModalApp = new Vue({
|
||||||
|
|||||||
@@ -1,20 +1,31 @@
|
|||||||
{{define "qrcodeModal"}}
|
{{define "qrcodeModal"}}
|
||||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||||
:closable="true"
|
:dialog-style="{ top: '20px' }"
|
||||||
:class="themeSwitcher.currentTheme"
|
:closable="true"
|
||||||
:footer="null"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="300px">
|
:footer="null"
|
||||||
|
width="300px">
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||||
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<template v-if="app.subSettings.enable && qrModal.subId">
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
<a-divider>Subscription</a-divider>
|
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
|
||||||
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas></div>
|
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
|
||||||
|
id="qrCode-sub"
|
||||||
|
class="qr-bg">
|
||||||
|
</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>
|
</template>
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<template v-for="(row, index) in qrModal.qrcodes">
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div>
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)"
|
||||||
|
:id="'qrCode-'+index"
|
||||||
|
class="qr-bg"></canvas>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
@@ -35,12 +46,21 @@
|
|||||||
this.client = client;
|
this.client = client;
|
||||||
this.subId = '';
|
this.subId = '';
|
||||||
this.qrcodes = [];
|
this.qrcodes = [];
|
||||||
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
if (this.inbound.protocol == Protocols.WIREGUARD){
|
||||||
this.qrcodes.push({
|
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
|
||||||
remark: l.remark,
|
this.qrcodes.push({
|
||||||
link: l.link
|
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;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
@@ -73,12 +93,16 @@
|
|||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
return app.subSettings.subURI+subID;
|
return app.subSettings.subURI+subID;
|
||||||
|
},
|
||||||
|
genSubJsonLink(subID) {
|
||||||
|
return app.subSettings.subJsonURI+subID;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
if (qrModal.client && qrModal.client.subId) {
|
if (qrModal.client && qrModal.client.subId) {
|
||||||
qrModal.subId = qrModal.client.subId;
|
qrModal.subId = qrModal.client.subId;
|
||||||
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
||||||
|
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
|
||||||
}
|
}
|
||||||
qrModal.qrcodes.forEach((element, index) => {
|
qrModal.qrcodes.forEach((element, index) => {
|
||||||
this.setQrCode("qrCode-" + index, element.link);
|
this.setQrCode("qrCode-" + index, element.link);
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
{{define "textModal"}}
|
{{define "textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
:closable="true"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme">
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
<template slot="footer">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||||
:download="txtModal.fileName">
|
:download="txtModal.fileName">[[ txtModal.fileName ]]
|
||||||
{{ i18n "download" }} [[ txtModal.fileName ]]
|
</a-button>
|
||||||
</a-button>
|
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
|
||||||
<a-input type="textarea" v-model="txtModal.content"
|
</template>
|
||||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
<a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content"
|
||||||
|
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
this.visible = true;
|
this.visible = true;
|
||||||
textModalApp.$nextTick(() => {
|
textModalApp.$nextTick(() => {
|
||||||
if (this.clipboard === null) {
|
if (this.clipboard === null) {
|
||||||
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
this.clipboard = new ClipboardJS('#copy-btn', {
|
||||||
text: () => this.content,
|
text: () => this.content,
|
||||||
});
|
});
|
||||||
this.clipboard.on('success', () => {
|
this.clipboard.on('success', () => {
|
||||||
@@ -52,4 +53,4 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -2,9 +2,14 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<style>
|
<style>
|
||||||
|
html * {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 20px 0 50px 0;
|
/* margin: 20px 0 50px 0;*/
|
||||||
|
height: 110px;
|
||||||
}
|
}
|
||||||
.ant-btn,
|
.ant-btn,
|
||||||
.ant-input {
|
.ant-input {
|
||||||
@@ -31,7 +36,9 @@
|
|||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: bold;
|
}
|
||||||
|
.title b {
|
||||||
|
font-weight: bold !important;
|
||||||
}
|
}
|
||||||
#app {
|
#app {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -42,6 +49,9 @@
|
|||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
padding: 3rem;
|
padding: 3rem;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
user-select:none;
|
||||||
|
-webkit-user-select:none;
|
||||||
|
-moz-user-select: none;
|
||||||
}
|
}
|
||||||
#login:hover {
|
#login:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||||
@@ -61,13 +71,13 @@
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
.dark .under {
|
.dark .under {
|
||||||
background-color: #0f2d32;
|
background-color: var(--dark-color-login-wave);
|
||||||
}
|
}
|
||||||
.dark #login {
|
.dark #login {
|
||||||
background-color: #151f31;
|
background-color: var(--dark-color-surface-100);
|
||||||
}
|
}
|
||||||
.dark h1 {
|
.dark h1 {
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255);
|
||||||
}
|
}
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -192,7 +202,7 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.dark .waves-header {
|
.dark .waves-header {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-login-background);
|
||||||
}
|
}
|
||||||
.waves-inner-header {
|
.waves-inner-header {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
@@ -204,7 +214,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 15vh;
|
height: 15vh;
|
||||||
margin-bottom: -5px; /*Fix for safari gap*/
|
margin-bottom: -8px; /*Fix for safari gap*/
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
}
|
}
|
||||||
@@ -212,23 +222,27 @@
|
|||||||
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
||||||
}
|
}
|
||||||
.dark .parallax > use {
|
.dark .parallax > use {
|
||||||
fill: rgb(10 117 87 / 20%);
|
fill: var(--dark-color-login-wave);
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(1) {
|
.parallax > use:nth-child(1) {
|
||||||
animation-delay: -2s;
|
animation-delay: -2s;
|
||||||
animation-duration: 7s;
|
animation-duration: 4s;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(2) {
|
.parallax > use:nth-child(2) {
|
||||||
animation-delay: -3s;
|
animation-delay: -3s;
|
||||||
animation-duration: 10s;
|
animation-duration: 7s;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(3) {
|
.parallax > use:nth-child(3) {
|
||||||
animation-delay: -4s;
|
animation-delay: -4s;
|
||||||
animation-duration: 13s;
|
animation-duration: 10s;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(4) {
|
||||||
|
animation-delay: -5s;
|
||||||
|
animation-duration: 13s;
|
||||||
|
}
|
||||||
@keyframes move-forever {
|
@keyframes move-forever {
|
||||||
0% {
|
0% {
|
||||||
transform: translate3d(-90px, 0, 0);
|
transform: translate3d(-90px, 0, 0);
|
||||||
@@ -243,90 +257,221 @@
|
|||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.words-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.words-wrapper b {
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.words-wrapper b.is-visible {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.headline.zoom .words-wrapper {
|
||||||
|
-webkit-perspective: 300px;
|
||||||
|
-moz-perspective: 300px;
|
||||||
|
perspective: 300px;
|
||||||
|
}
|
||||||
|
.headline {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.headline.zoom b {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.headline.zoom b.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-animation: zoom-in 0.8s;
|
||||||
|
-moz-animation: zoom-in 0.8s;
|
||||||
|
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-in 0.8s;
|
||||||
|
}
|
||||||
|
.headline.zoom b.is-hidden {
|
||||||
|
-webkit-animation: zoom-out 0.8s;
|
||||||
|
-moz-animation: zoom-out 0.8s;
|
||||||
|
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-out 0.4s;
|
||||||
|
}
|
||||||
|
@-webkit-keyframes zoom-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: translateZ(100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-moz-keyframes zoom-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
-moz-transform: translateZ(100px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
-moz-transform: translateZ(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes zoom-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: translateZ(100px);
|
||||||
|
-moz-transform: translateZ(100px);
|
||||||
|
-ms-transform: translateZ(100px);
|
||||||
|
-o-transform: translateZ(100px);
|
||||||
|
transform: translateZ(100px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
-moz-transform: translateZ(0);
|
||||||
|
-ms-transform: translateZ(0);
|
||||||
|
-o-transform: translateZ(0);
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes zoom-out {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: translateZ(-100px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-moz-keyframes zoom-out {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
-moz-transform: translateZ(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-moz-transform: translateZ(-100px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes zoom-out {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
-moz-transform: translateZ(0);
|
||||||
|
-ms-transform: translateZ(0);
|
||||||
|
-o-transform: translateZ(0);
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: translateZ(-100px);
|
||||||
|
-moz-transform: translateZ(-100px);
|
||||||
|
-ms-transform: translateZ(-100px);
|
||||||
|
-o-transform: translateZ(-100px);
|
||||||
|
transform: translateZ(-100px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-menu-item .anticon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.ant-menu-inline .ant-menu-item {
|
||||||
|
padding: 0 16px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content class="under" style="min-height: 0;">
|
<a-layout-content class="under" style="min-height: 0;">
|
||||||
<div class="waves-header">
|
<div class="waves-header">
|
||||||
<div class="waves-inner-header"></div>
|
<div class="waves-inner-header"></div>
|
||||||
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
<defs>
|
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
||||||
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
<defs>
|
||||||
</defs>
|
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
||||||
<g class="parallax">
|
</defs>
|
||||||
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" />
|
<g class="parallax">
|
||||||
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" />
|
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(0, 135, 113, 0.08)" />
|
||||||
<use xlink:href="#gentle-wave" x="48" y="7" fill="rgba(0, 135, 113, 0.08)" />
|
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" />
|
||||||
</g>
|
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" />
|
||||||
</svg>
|
<use xlink:href="#gentle-wave" x="48" y="7" fill="#c7ebe2" />
|
||||||
</div>
|
</g>
|
||||||
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;">
|
</svg>
|
||||||
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
|
</div>
|
||||||
|
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;">
|
||||||
|
<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-row type="flex" justify="center">
|
||||||
<a-col>
|
<a-col style="width: 100%;">
|
||||||
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
<h1 class="title headline zoom">
|
||||||
</a-col>
|
<span class="words-wrapper">
|
||||||
|
<b class="is-visible">{{ i18n "pages.login.hello" }}</b>
|
||||||
|
<b>{{ i18n "pages.login.title" }}</b>
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col span="24">
|
<a-col span="24">
|
||||||
<a-form>
|
<a-form>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
<a-input autocomplete="username" v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
||||||
@keydown.enter.native="login" autofocus>
|
@keydown.enter.native="login" autofocus>
|
||||||
<a-icon slot="prefix" type="user" style="font-size: 16px;"/>
|
<a-icon slot="prefix" type="user" style="font-size: 16px;"></a-icon>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<password-input icon="lock" v-model.trim="user.password"
|
<password-input autocomplete="current-password" icon="lock" v-model.trim="user.password"
|
||||||
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
placeholder='{{ i18n "password" }}'
|
||||||
</password-input>
|
@keydown.enter.native="login">
|
||||||
</a-form-item>
|
</password-input>
|
||||||
<a-form-item v-if="secretEnable">
|
</a-form-item>
|
||||||
<password-input icon="key" v-model.trim="user.loginSecret"
|
<a-form-item v-if="secretEnable">
|
||||||
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
|
<password-input autocomplete="secret" icon="key" v-model.trim="user.loginSecret"
|
||||||
</password-input>
|
placeholder='{{ i18n "secretToken" }}'
|
||||||
</a-input>
|
@keydown.enter.native="login">
|
||||||
</a-form-item>
|
</password-input>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-form-item>
|
||||||
<div class="wave-btn-bg wave-btn-bg-cl" :style="loading ? { width: '54px' } : { display: 'inline-block' }">
|
<a-row justify="center" class="centered">
|
||||||
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined">
|
<div style="height: 50px;" class="wave-btn-bg wave-btn-bg-cl"
|
||||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
:style="loading ? { width: '52px' } : { display: 'inline-block' }">
|
||||||
</a-button>
|
<a-button class="ant-btn-primary-login" type="primary"
|
||||||
</div>
|
:loading="loading" @click="login"
|
||||||
</a-row>
|
:icon="loading ? 'poweroff' : undefined">
|
||||||
</a-form-item>
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
<a-form-item>
|
</a-button>
|
||||||
<a-row justify="center" class="centered">
|
</div>
|
||||||
<a-col :span="24">
|
</a-row>
|
||||||
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
</a-form-item>
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
<a-form-item>
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<a-row justify="center" class="centered">
|
||||||
<span v-text="l.name"></span>
|
<a-col :span="24">
|
||||||
</a-select-option>
|
<a-select ref="selectLang" v-model="lang"
|
||||||
</a-select>
|
@change="setLang(lang)" style="width: 150px;"
|
||||||
</a-col>
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
</a-row>
|
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||||
</a-form-item>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<a-form-item>
|
<span v-text="l.name"></span>
|
||||||
<a-row justify="center" class="centered">
|
</a-select-option>
|
||||||
<a-col>
|
</a-select>
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
</a-col>
|
||||||
</a-col>
|
</a-row>
|
||||||
<a-col>
|
</a-form-item>
|
||||||
<theme-switch />
|
<a-form-item>
|
||||||
</a-col>
|
<a-row justify="center" class="centered">
|
||||||
</a-row>
|
<theme-switch></theme-switch>
|
||||||
</a-form-item>
|
</a-row>
|
||||||
</a-form>
|
</a-form-item>
|
||||||
</a-col>
|
</a-form>
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</transition>
|
</transition>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "component/password" .}}
|
{{template "component/password" .}}
|
||||||
@@ -372,6 +517,42 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var animationDelay = 2000;
|
||||||
|
initHeadline();
|
||||||
|
|
||||||
|
function initHeadline() {
|
||||||
|
animateHeadline(document.querySelectorAll('.headline'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function animateHeadline(headlines) {
|
||||||
|
var duration = animationDelay;
|
||||||
|
headlines.forEach(function(headline) {
|
||||||
|
setTimeout(function() {
|
||||||
|
hideWord(headline.querySelector('.is-visible'));
|
||||||
|
}, duration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideWord(word) {
|
||||||
|
var nextWord = takeNext(word);
|
||||||
|
switchWord(word, nextWord);
|
||||||
|
setTimeout(function() {
|
||||||
|
hideWord(nextWord);
|
||||||
|
}, animationDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function takeNext(word) {
|
||||||
|
return (word.nextElementSibling) ? word.nextElementSibling : word.parentElement.firstElementChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchWord(oldWord, newWord) {
|
||||||
|
oldWord.classList.remove('is-visible');
|
||||||
|
oldWord.classList.add('is-hidden');
|
||||||
|
newWord.classList.remove('is-hidden');
|
||||||
|
newWord.classList.add('is-visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker v-if="datepicker == 'gregorian'" :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>
|
:dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker>
|
||||||
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme"
|
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||||
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker>
|
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
|
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
clientsBulkModal.visible = false;
|
clientsBulkModal.visible = false;
|
||||||
clientsBulkModal.loading(false);
|
clientsBulkModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
clientsBulkModal.confirmLoading = loading;
|
clientsBulkModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
clientModal.visible = false;
|
clientModal.visible = false;
|
||||||
clientModal.loading(false);
|
clientModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
clientModal.confirmLoading = loading;
|
clientModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,39 +1,30 @@
|
|||||||
{{define "menuItems"}}
|
{{define "menuItems"}}
|
||||||
<a-menu-item key="{{ .base_path }}panel/">
|
<a-menu-item key="{{ .base_path }}panel/">
|
||||||
<a-icon type="dashboard"></a-icon>
|
<a-icon type="dashboard"></a-icon>
|
||||||
<span>{{ i18n "menu.dashboard"}}</span>
|
<span><b>{{ i18n "menu.dashboard"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
||||||
<a-icon type="user"></a-icon>
|
<a-icon type="user"></a-icon>
|
||||||
<span>{{ i18n "menu.inbounds"}}</span>
|
<span><b>{{ i18n "menu.inbounds"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/settings">
|
<a-menu-item key="{{ .base_path }}panel/settings">
|
||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>{{ i18n "menu.settings"}}</span>
|
<span><b>{{ i18n "menu.settings"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/xray">
|
<a-menu-item key="{{ .base_path }}panel/xray">
|
||||||
<a-icon type="tool"></a-icon>
|
<a-icon type="tool"></a-icon>
|
||||||
<span>{{ i18n "menu.xray"}}</span>
|
<span><b>{{ i18n "menu.xray"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
|
||||||
<!-- <span>Client</span>-->
|
|
||||||
<!--</a-menu-item>-->
|
|
||||||
<a-menu-item key="{{ .base_path }}logout">
|
<a-menu-item key="{{ .base_path }}logout">
|
||||||
<a-icon type="logout"></a-icon>
|
<a-icon type="logout"></a-icon>
|
||||||
<span>{{ i18n "menu.logout"}}</span>
|
<span><b>{{ i18n "menu.logout"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
{{define "commonSider"}}
|
{{define "commonSider"}}
|
||||||
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<theme-switch></theme-switch>
|
||||||
<a-menu-item mode="inline">
|
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
|
||||||
<theme-switch />
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
@@ -47,12 +38,7 @@
|
|||||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||||
</div>
|
</div>
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<theme-switch></theme-switch>
|
||||||
<a-menu-item mode="inline">
|
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
|
||||||
<theme-switch />
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker"
|
<a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker"
|
||||||
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
||||||
placeholder="انتخاب تاریخ">
|
:placeholder="placeholder">
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-icon type="calendar" style="font-size: 16px;"/>
|
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;"/>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
const persianDatepicker = {};
|
const persianDatepicker = {};
|
||||||
|
|
||||||
Vue.component('persian-datepicker', {
|
Vue.component('persian-datepicker', {
|
||||||
props: ['dropdown-class-name', 'format', 'value'],
|
props: ['placeholder', 'format', 'value'],
|
||||||
template: `{{template "component/persianDatepickerTemplate"}}`,
|
template: `{{template "component/persianDatepickerTemplate"}}`,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
listenToDatepicker() {
|
listenToDatepicker() {
|
||||||
jalaliDatepicker.startWatch({
|
jalaliDatepicker.startWatch({
|
||||||
time: true,
|
time: true,
|
||||||
container: '.ant-modal-wrap',
|
zIndex: '9999',
|
||||||
hideAfterChange: true,
|
hideAfterChange: true,
|
||||||
useDropDownYears: false,
|
useDropDownYears: false,
|
||||||
changeMonthRotateYear: true,
|
changeMonthRotateYear: true,
|
||||||
@@ -57,4 +57,4 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
|
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'number'">
|
<template v-else-if="type === 'number'">
|
||||||
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" :max="max" style="width: 100%;"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'switch'">
|
<template v-else-if="type === 'switch'">
|
||||||
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
{{define "component/setting"}}
|
{{define "component/setting"}}
|
||||||
<script>
|
<script>
|
||||||
Vue.component('setting-list-item', {
|
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"}}`,
|
template: `{{template "component/settingListItem"}}`,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
236
web/html/xui/component/sortableTable.html
Normal file
236
web/html/xui/component/sortableTable.html
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
{{define "component/sortableTableTrigger"}}
|
||||||
|
<a-icon type="drag"
|
||||||
|
class="sortable-icon"
|
||||||
|
style="cursor: move;"
|
||||||
|
@mouseup="mouseUpHandler"
|
||||||
|
@mousedown="mouseDownHandler"
|
||||||
|
@click="clickHandler" />
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "component/sortableTable"}}
|
||||||
|
<script>
|
||||||
|
const DRAGGABLE_ROW_CLASS = 'draggable-row';
|
||||||
|
|
||||||
|
const findParentRowElement = (el) => {
|
||||||
|
if (!el || !el.tagName) {
|
||||||
|
return null;
|
||||||
|
} else if (el.classList.contains(DRAGGABLE_ROW_CLASS)) {
|
||||||
|
return el;
|
||||||
|
} else if (el.parentNode) {
|
||||||
|
return findParentRowElement(el.parentNode);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.component('a-table-sortable', {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sortingElementIndex: null,
|
||||||
|
newElementIndex: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ['data-source', 'customRow'],
|
||||||
|
inheritAttrs: false,
|
||||||
|
provide() {
|
||||||
|
const sortable = {}
|
||||||
|
|
||||||
|
Object.defineProperty(sortable, "setSortableIndex", {
|
||||||
|
enumerable: true,
|
||||||
|
get: () => this.setCurrentSortableIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(sortable, "resetSortableIndex", {
|
||||||
|
enumerable: true,
|
||||||
|
get: () => this.resetSortableIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sortable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function (createElement) {
|
||||||
|
return createElement('a-table', {
|
||||||
|
class: {
|
||||||
|
'ant-table-is-sorting': this.isDragging(),
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
...this.$attrs,
|
||||||
|
'data-source': this.records,
|
||||||
|
customRow: (record, index) => this.customRowRender(record, index),
|
||||||
|
},
|
||||||
|
on: this.$listeners,
|
||||||
|
nativeOn: {
|
||||||
|
drop: (e) => this.dropHandler(e),
|
||||||
|
},
|
||||||
|
scopedSlots: this.$scopedSlots,
|
||||||
|
}, this.$slots.default, )
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$memoSort = {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isDragging() {
|
||||||
|
const currentIndex = this.sortingElementIndex;
|
||||||
|
return currentIndex !== null && currentIndex !== undefined;
|
||||||
|
},
|
||||||
|
resetSortableIndex(e, index) {
|
||||||
|
this.sortingElementIndex = null;
|
||||||
|
this.newElementIndex = null;
|
||||||
|
this.$memoSort = {};
|
||||||
|
},
|
||||||
|
setCurrentSortableIndex(e, index) {
|
||||||
|
this.sortingElementIndex = index;
|
||||||
|
},
|
||||||
|
dragStartHandler(e, index) {
|
||||||
|
if (!this.isDragging()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hideDragImage = this.$el.cloneNode(true);
|
||||||
|
hideDragImage.id = "hideDragImage-hide";
|
||||||
|
hideDragImage.style.opacity = 0;
|
||||||
|
e.dataTransfer.setDragImage(hideDragImage, 0, 0);
|
||||||
|
},
|
||||||
|
dragStopHandler(e, index) {
|
||||||
|
const hideDragImage = document.getElementById('hideDragImage-hide');
|
||||||
|
if (hideDragImage) hideDragImage.remove();
|
||||||
|
this.resetSortableIndex(e, index);
|
||||||
|
},
|
||||||
|
dragOverHandler(e, index) {
|
||||||
|
if (!this.isDragging()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const currentIndex = this.sortingElementIndex;
|
||||||
|
if (index === currentIndex) {
|
||||||
|
this.newElementIndex = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = findParentRowElement(e.target);
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = row.getBoundingClientRect();
|
||||||
|
const offsetTop = e.pageY - rect.top;
|
||||||
|
|
||||||
|
if (offsetTop < rect.height / 2) {
|
||||||
|
this.newElementIndex = Math.max(index - 1, 0);
|
||||||
|
} else {
|
||||||
|
this.newElementIndex = index;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dropHandler(e) {
|
||||||
|
if (this.isDragging()) {
|
||||||
|
this.$emit('onsort', this.sortingElementIndex, this.newElementIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customRowRender(record, index) {
|
||||||
|
const parentMethodResult = this.customRow?.(record, index) || {};
|
||||||
|
const newIndex = this.newElementIndex;
|
||||||
|
const currentIndex = this.sortingElementIndex;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...parentMethodResult,
|
||||||
|
attrs: {
|
||||||
|
...(parentMethodResult?.attrs || {}),
|
||||||
|
draggable: true,
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
...(parentMethodResult?.on || {}),
|
||||||
|
dragstart: (e) => this.dragStartHandler(e, index),
|
||||||
|
dragend: (e) => this.dragStopHandler(e, index),
|
||||||
|
dragover: (e) => this.dragOverHandler(e, index),
|
||||||
|
},
|
||||||
|
class: {
|
||||||
|
...(parentMethodResult?.class || {}),
|
||||||
|
[DRAGGABLE_ROW_CLASS]: true,
|
||||||
|
['dragging']: this.isDragging()
|
||||||
|
? (newIndex === null ? index === currentIndex : index === newIndex)
|
||||||
|
: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
records() {
|
||||||
|
const newIndex = this.newElementIndex;
|
||||||
|
const currentIndex = this.sortingElementIndex;
|
||||||
|
|
||||||
|
if (!this.isDragging() || newIndex === null || currentIndex === newIndex) {
|
||||||
|
return this.dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$memoSort.newIndex === newIndex) {
|
||||||
|
return this.$memoSort.list;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = [...this.dataSource];
|
||||||
|
list.splice(newIndex, 0, list.splice(currentIndex, 1)[0]);
|
||||||
|
|
||||||
|
this.$memoSort = {
|
||||||
|
newIndex,
|
||||||
|
list,
|
||||||
|
};
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('table-sort-trigger', {
|
||||||
|
template: `{{template "component/sortableTableTrigger"}}`,
|
||||||
|
props: ['item-index'],
|
||||||
|
inject: ['sortable'],
|
||||||
|
methods: {
|
||||||
|
mouseDownHandler(e) {
|
||||||
|
if (this.sortable) {
|
||||||
|
this.sortable.setSortableIndex(e, this.itemIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mouseUpHandler(e) {
|
||||||
|
if (this.sortable) {
|
||||||
|
this.sortable.resetSortableIndex(e, this.itemIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickHandler(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media only screen and (max-width: 767px) {
|
||||||
|
.sortable-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-table-is-sorting .draggable-row td {
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
}
|
||||||
|
.dark .ant-table-is-sorting .draggable-row td {
|
||||||
|
background-color: var(--dark-color-surface-100) !important;
|
||||||
|
}
|
||||||
|
.ant-table-is-sorting .dragging td {
|
||||||
|
background-color: rgb(232 244 242) !important;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.dark .ant-table-is-sorting .dragging td {
|
||||||
|
background-color: var(--dark-color-table-hover) !important;
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
.ant-table-is-sorting .dragging {
|
||||||
|
opacity: 1;
|
||||||
|
box-shadow: 1px -2px 2px #008771;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.ant-table-is-sorting .dragging .ant-table-row-index {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{end}}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
{{define "component/themeSwitchTemplate"}}
|
{{define "component/themeSwitchTemplate"}}
|
||||||
<template>
|
<template>
|
||||||
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
@change="themeSwitcher.toggleTheme()">
|
<a-menu-item mode="inline" class="ant-menu-theme-switch">
|
||||||
</a-switch>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
|
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||||
|
<template v-if="themeSwitcher.isDarkTheme">
|
||||||
|
<a-checkbox style="margin-left: 1rem; vertical-align: middle;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()">Ultra</a-checkbox>
|
||||||
|
</template>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
@@ -10,25 +16,48 @@
|
|||||||
<script>
|
<script>
|
||||||
function createThemeSwitcher() {
|
function createThemeSwitcher() {
|
||||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||||
|
const isUltra = localStorage.getItem('isUltraDarkThemeEnabled') === 'true';
|
||||||
|
if (isUltra) {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
||||||
|
}
|
||||||
const theme = isDarkTheme ? 'dark' : 'light';
|
const theme = isDarkTheme ? 'dark' : 'light';
|
||||||
|
document.querySelector('body').setAttribute('class', theme);
|
||||||
return {
|
return {
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
|
isUltra,
|
||||||
get currentTheme() {
|
get currentTheme() {
|
||||||
return this.isDarkTheme ? 'dark' : 'light';
|
return this.isDarkTheme ? 'dark' : 'light';
|
||||||
},
|
},
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
this.isDarkTheme = !this.isDarkTheme;
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
|
document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light');
|
||||||
|
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||||
},
|
},
|
||||||
|
toggleUltra() {
|
||||||
|
this.isUltra = !this.isUltra;
|
||||||
|
if (this.isUltra) {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.removeAttribute('data-theme');
|
||||||
|
}
|
||||||
|
localStorage.setItem('isUltraDarkThemeEnabled', this.isUltra.toString());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeSwitcher = createThemeSwitcher();
|
const themeSwitcher = createThemeSwitcher();
|
||||||
|
|
||||||
Vue.component('theme-switch', {
|
Vue.component('theme-switch', {
|
||||||
props: [],
|
props: [],
|
||||||
template: `{{template "component/themeSwitchTemplate"}}`,
|
template: `{{template "component/themeSwitchTemplate"}}`,
|
||||||
data: () => ({ themeSwitcher }),
|
data: () => ({
|
||||||
|
themeSwitcher
|
||||||
|
}),
|
||||||
|
mounted() {
|
||||||
|
this.$message.config({
|
||||||
|
getContainer: () => document.getElementById('message')
|
||||||
|
});
|
||||||
|
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
86
web/html/xui/dns_modal.html
Normal file
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:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
||||||
|
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
|
||||||
|
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
|
||||||
|
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
|
||||||
|
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
|
||||||
|
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</template>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
||||||
|
<a-select
|
||||||
|
v-model="dnsModal.dnsServer.queryStrategy"
|
||||||
|
style="width: 100%"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
||||||
|
[[ l ]]
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</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
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:8} }" :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-number style="width: 100%;" type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input-number>
|
||||||
|
</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}}
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker v-if="datepicker == 'gregorian'" :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>
|
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
|
||||||
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme"
|
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||||
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker>
|
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker>
|
||||||
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/inbound"}}
|
{{define "form/inbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "enable" }}'>
|
<a-form-item label='{{ i18n "enable" }}'>
|
||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number v-model.number="inbound.port"></a-input-number>
|
<a-input-number v-model.number="inbound.port" :min="1" :max="65531"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -54,11 +54,11 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="dbInbound._expiryTime"></a-date-picker>
|
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||||
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme"
|
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||||
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"></persian-datepicker>
|
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"></persian-datepicker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- sniffing -->
|
<!-- sniffing -->
|
||||||
<template v-if="inbound.canSniffing()">
|
<template>
|
||||||
{{template "form/sniffing"}}
|
{{template "form/sniffing"}}
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -171,7 +171,6 @@
|
|||||||
<a-form-item label='ID'>
|
<a-form-item label='ID'>
|
||||||
<a-input v-model.trim="outbound.settings.id"></a-input>
|
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- vless settings -->
|
<!-- vless settings -->
|
||||||
<template v-if="outbound.canEnableTlsFlow()">
|
<template v-if="outbound.canEnableTlsFlow()">
|
||||||
<a-form-item label='Flow'>
|
<a-form-item label='Flow'>
|
||||||
@@ -194,50 +193,55 @@
|
|||||||
<a-input v-model.trim="outbound.settings.pass"></a-input>
|
<a-input v-model.trim="outbound.settings.pass"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- trojan/shadowsocks -->
|
||||||
<!-- shadowsocks -->
|
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
||||||
<template v-if="outbound.protocol === Protocols.Shadowsocks">
|
<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">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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-option v-for="(method, method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='UDP over TCP'>
|
<a-form-item label='UDP over TCP'>
|
||||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
<template v-if="outbound.canEnableStream()">
|
<template v-if="outbound.canEnableStream()">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="kcp">mKCP</a-select-option>
|
<a-select-option value="kcp">mKCP</a-select-option>
|
||||||
<a-select-option value="ws">WS</a-select-option>
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
<a-select-option value="http">H2</a-select-option>
|
<a-select-option value="http">H2</a-select-option>
|
||||||
<a-select-option value="quic">QUIC</a-select-option>
|
<a-select-option value="quic">QUIC</a-select-option>
|
||||||
<a-select-option value="grpc">gRPC</a-select-option>
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
</a-select>
|
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
<template v-if="outbound.stream.network === 'tcp'">
|
<template v-if="outbound.stream.network === 'tcp'">
|
||||||
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
||||||
<a-switch
|
<a-switch :checked="outbound.stream.tcp.type === 'http'"
|
||||||
:checked="outbound.stream.tcp.type === 'http'"
|
|
||||||
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.stream.tcp.type == 'http'">
|
<template v-if="outbound.stream.tcp.type == 'http'">
|
||||||
<a-form-item label='{{ i18n "host" }}'>
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
<a-input v-model.trim="outbound.stream.tcp.host"></a-input>
|
<a-input v-model.trim="outbound.stream.tcp.host"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="outbound.stream.tcp.path"></a-input>
|
<a-input v-model.trim="outbound.stream.tcp.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- kcp -->
|
<!-- kcp -->
|
||||||
<template v-if="outbound.stream.network === 'kcp'">
|
<template v-if="outbound.stream.network === 'kcp'">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
@@ -261,7 +265,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Uplink (MB/s)'>
|
<a-form-item label='Uplink (MB/s)'>
|
||||||
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
|
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Downlink (MB/s)'>
|
<a-form-item label='Downlink (MB/s)'>
|
||||||
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
|
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -275,7 +279,7 @@
|
|||||||
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
|
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- ws -->
|
<!-- ws -->
|
||||||
<template v-if="outbound.stream.network === 'ws'">
|
<template v-if="outbound.stream.network === 'ws'">
|
||||||
<a-form-item label='{{ i18n "host" }}'>
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
@@ -283,9 +287,9 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
<a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- http -->
|
<!-- http -->
|
||||||
<template v-if="outbound.stream.network === 'http'">
|
<template v-if="outbound.stream.network === 'http'">
|
||||||
<a-form-item label='{{ i18n "host" }}'>
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
@@ -295,7 +299,7 @@
|
|||||||
<a-input v-model.trim="outbound.stream.http.path"></a-input>
|
<a-input v-model.trim="outbound.stream.http.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- quic -->
|
<!-- quic -->
|
||||||
<template v-if="outbound.stream.network === 'quic'">
|
<template v-if="outbound.stream.network === 'quic'">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||||
@@ -307,7 +311,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-input v-model.trim="outbound.stream.quic.key"></a-input>
|
<a-input v-model.trim="outbound.stream.quic.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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="none">None</a-select-option>
|
||||||
@@ -319,7 +323,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- grpc -->
|
<!-- grpc -->
|
||||||
<template v-if="outbound.stream.network === 'grpc'">
|
<template v-if="outbound.stream.network === 'grpc'">
|
||||||
<a-form-item label='Service Name'>
|
<a-form-item label='Service Name'>
|
||||||
@@ -329,6 +333,16 @@
|
|||||||
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</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-form-item><a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
@@ -337,7 +351,7 @@
|
|||||||
<a-radio-group v-model="outbound.stream.security" button-style="solid">
|
<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="none">{{ i18n "none" }}</a-radio-button>
|
||||||
<a-radio-button value="tls">TLS</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-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.stream.isTls">
|
<template v-if="outbound.stream.isTls">
|
||||||
@@ -345,13 +359,15 @@
|
|||||||
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
|
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="outbound.stream.tls.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="outbound.stream.tls.fingerprint"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<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-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ALPN">
|
<a-form-item label="ALPN">
|
||||||
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
|
<a-select mode="multiple"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="outbound.stream.tls.alpn">
|
v-model="outbound.stream.tls.alpn">
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -363,11 +379,12 @@
|
|||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
<template v-if="outbound.stream.isReality">
|
<template v-if="outbound.stream.isReality">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label="SNI">
|
||||||
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="outbound.stream.reality.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -382,6 +399,46 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 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 -->
|
||||||
|
<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>
|
||||||
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/dokodemo"}}
|
{{define "form/dokodemo"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
||||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form>
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
@@ -18,4 +18,4 @@
|
|||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</template>
|
</template>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/socks"}}
|
{{define "form/socks"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
||||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
|
|||||||
@@ -19,27 +19,23 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
||||||
<a-button type="primary" size="small"
|
|
||||||
@click="inbound.settings.addFallback()">
|
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Fallback [[ index + 1 ]]
|
Fallback [[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;" />
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='ALPN'>
|
<a-form-item label='ALPN'>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-input v-model="fallback.alpn"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -53,6 +49,6 @@
|
|||||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:0;"></a-divider>
|
<a-divider style="margin:5px 0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -21,27 +21,23 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
||||||
<a-button type="primary" size="small"
|
|
||||||
@click="inbound.settings.addFallback()">
|
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Fallback [[ index + 1 ]]
|
Fallback [[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;" />
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='ALPN'>
|
<a-form-item label='ALPN'>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-input v-model="fallback.alpn"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/wireguard"}}
|
{{define "form/wireguard"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -26,16 +26,40 @@
|
|||||||
<a-form-item label="Peers">
|
<a-form-item label="Peers">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
||||||
</a-form-item>
|
</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-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Peer [[ index + 1 ]]
|
Peer [[ index + 1 ]]
|
||||||
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
|
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
|
<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-input v-model.trim="peer.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
|
<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-input v-model.trim="peer.psk"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
<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-button style="width: 10%; margin: 0px; border-radius: 0 1rem 1rem 0;" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Service Name">
|
<a-form-item label="Service Name">
|
||||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||||
</a-form-item>
|
</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-form-item label="Multi Mode">
|
||||||
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/streamHTTP"}}
|
{{define "form/streamHTTP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
13
web/html/xui/form/stream/stream_httpupgrade.html
Normal file
13
web/html/xui/form/stream/stream_httpupgrade.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{{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>
|
||||||
|
{{end}}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP</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="utp">uTP</a-select-option>
|
||||||
@@ -23,25 +23,25 @@
|
|||||||
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
|
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='MTU'>
|
<a-form-item label='MTU'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576" :max="1460"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='TTI (ms)'>
|
<a-form-item label='TTI (ms)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10" :max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Uplink (MB/s)'>
|
<a-form-item label='Uplink (MB/s)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.upCap" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Downlink (MB/s)'>
|
<a-form-item label='Downlink (MB/s)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.downCap" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Congestion'>
|
<a-form-item label='Congestion'>
|
||||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Read Buffer (MB)'>
|
<a-form-item label='Read Buffer (MB)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.readBuffer" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Write Buffer (MB)'>
|
<a-form-item label='Write Buffer (MB)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<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 v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.quic.type" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP</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="utp">uTP</a-select-option>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" style="width: 75%" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="kcp">mKCP</a-select-option>
|
<a-select-option value="kcp">mKCP</a-select-option>
|
||||||
<a-select-option value="ws">WS</a-select-option>
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
<a-select-option value="http">H2</a-select-option>
|
<a-select-option value="http">H2</a-select-option>
|
||||||
<a-select-option value="quic">QUIC</a-select-option>
|
<a-select-option value="quic">QUIC</a-select-option>
|
||||||
<a-select-option value="grpc">gRPC</a-select-option>
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
|
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -43,6 +44,12 @@
|
|||||||
<template v-if="inbound.stream.network === 'grpc'">
|
<template v-if="inbound.stream.network === 'grpc'">
|
||||||
{{template "form/streamGRPC"}}
|
{{template "form/streamGRPC"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- httpupgrade -->
|
||||||
|
<template v-if="inbound.stream.network === 'httpupgrade'">
|
||||||
|
{{template "form/streamHTTPUpgrade"}}
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- sockopt -->
|
<!-- sockopt -->
|
||||||
<template>
|
<template>
|
||||||
{{template "form/streamSockopt"}}
|
{{template "form/streamSockopt"}}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('host', '')">+</a-button>
|
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
@@ -79,4 +79,4 @@
|
|||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
|
<a-button size="small" @click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
|
|||||||
@@ -34,16 +34,16 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Min/Max Version">
|
<a-form-item label="Min/Max Version">
|
||||||
<a-input-group compact>
|
<a-input-group compact>
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.minVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<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-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
@@ -73,10 +73,10 @@
|
|||||||
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<template v-if="cert.useFile">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="cert.certFile"></a-input>
|
<a-input v-model.trim="cert.certFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
<a-input v-model.trim="cert.keyFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
@@ -85,10 +85,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -124,10 +124,10 @@
|
|||||||
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<template v-if="cert.useFile">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="cert.certFile"></a-input>
|
<a-input v-model.trim="cert.certFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
<a-input v-model.trim="cert.keyFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
@@ -136,10 +136,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='uTLS'>
|
<a-form-item label='uTLS'>
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -180,10 +180,10 @@
|
|||||||
<a-form-item label='SpiderX'>
|
<a-form-item label='SpiderX'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Private Key'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Public Key'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template slot="online" slot-scope="text, client, index">
|
<template slot="online" slot-scope="text, client, index">
|
||||||
<template v-if="isClientOnline(client.email)">
|
<template v-if="client.enable && isClientOnline(client.email)">
|
||||||
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
||||||
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||||
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template>
|
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||||
</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-badge>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade ">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "host" }}</td>
|
<td>{{ i18n "host" }}</td>
|
||||||
<td v-if="inbound.host">
|
<td v-if="inbound.host">
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||||
<a-divider>Subscription URL</a-divider>
|
<a-divider>Subscription URL</a-divider>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
<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-col :sx="24" :md="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
||||||
@@ -175,14 +175,24 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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; margin-top: 5px;">
|
||||||
|
<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>
|
||||||
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
<a-divider>Telegram ID</a-divider>
|
<a-divider>Telegram ID</a-divider>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sx="24" :md="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
<a-col :sx="24" :md="22">[[ infoModal.clientSettings.tgId ]]</a-col>
|
||||||
<a-col :sx="24" :md="2" style="text-align: right;">
|
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
|
<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>
|
<a-icon type="snippets"></a-icon>
|
||||||
</button>
|
</button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -283,24 +293,50 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<template v-for="(peer, index) in inbound.settings.peers">
|
<template v-for="(peer, index) in inbound.settings.peers">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><a-tag>Peer [[ index + 1 ]]</a-tag></td>
|
<td colspan="2"><a-divider>Peer [[ index + 1 ]]</a-divider></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<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>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
|
||||||
<td>[[ peer.publicKey ]]</td>
|
<td>[[ peer.publicKey ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr class="client-table-odd-row">
|
||||||
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
|
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
|
||||||
<td>[[ peer.psk ]]</td>
|
<td>[[ peer.psk ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<tr>
|
||||||
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
|
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
|
||||||
<td>[[ peer.allowedIPs.join(",") ]]</td>
|
<td>[[ peer.allowedIPs.join(",") ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr class="client-table-odd-row">
|
||||||
<td>Keep Alive</td>
|
<td>Keep Alive</td>
|
||||||
<td>[[ peer.keepAlive ]]</td>
|
<td>[[ peer.keepAlive ]]</td>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -319,7 +355,7 @@
|
|||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
subLink: '',
|
subLink: '',
|
||||||
tgLink: '',
|
subJsonLink: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
@@ -327,13 +363,15 @@
|
|||||||
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||||
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
|
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.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) {
|
||||||
if (this.clientSettings.subId) {
|
if (this.clientSettings.subId) {
|
||||||
this.subLink = this.genSubLink(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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
@@ -343,6 +381,9 @@
|
|||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
return app.subSettings.subURI+subID;
|
return app.subSettings.subURI+subID;
|
||||||
|
},
|
||||||
|
genSubJsonLink(subID) {
|
||||||
|
return app.subSettings.subJsonURI+subID;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{{define "inboundModal"}}
|
{{define "inboundModal"}}
|
||||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:dialog-style="{ top: '20px' }" @ok="inModal.ok"
|
||||||
:class="themeSwitcher.currentTheme"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
:class="themeSwitcher.currentTheme"
|
||||||
|
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
{{template "form/inbound"}}
|
{{template "form/inbound"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
inModal.visible = false;
|
inModal.visible = false;
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,9 +56,13 @@
|
|||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
</a-tag>
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
@@ -133,6 +137,10 @@
|
|||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export" }}
|
{{ i18n "pages.inbounds.export" }}
|
||||||
</a-menu-item>
|
</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-menu-item key="resetInbounds">
|
||||||
<a-icon type="reload"></a-icon>
|
<a-icon type="reload"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||||
@@ -141,7 +149,7 @@
|
|||||||
<a-icon type="file-done"></a-icon>
|
<a-icon type="file-done"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="delDepletedClients">
|
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
||||||
<a-icon type="rest"></a-icon>
|
<a-icon type="rest"></a-icon>
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -178,7 +186,7 @@
|
|||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<a-back-top></a-back-top>
|
<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"
|
:data-source="searchedInbounds"
|
||||||
:scroll="isMobile ? {} : { x: 1000 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination=pagination(searchedInbounds)
|
:pagination=pagination(searchedInbounds)
|
||||||
@@ -196,7 +204,7 @@
|
|||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</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>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -217,7 +225,11 @@
|
|||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export"}}
|
{{ i18n "pages.inbounds.export"}}
|
||||||
</a-menu-item>
|
</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>
|
<a-icon type="rest"></a-icon>
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -230,7 +242,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-menu-item key="clipboard">
|
<a-menu-item key="clipboard">
|
||||||
<a-icon type="copy"></a-icon>
|
<a-icon type="copy"></a-icon>
|
||||||
{{ i18n "pages.inbounds.copyToClipboard" }}
|
{{ i18n "pages.inbounds.exportInbound" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="resetTraffic">
|
<a-menu-item key="resetTraffic">
|
||||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
@@ -496,7 +508,7 @@
|
|||||||
scopedSlots: { customRender: 'expiryTime' },
|
scopedSlots: { customRender: 'expiryTime' },
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const mobileColums = [{
|
const mobileColumns = [{
|
||||||
title: "ID",
|
title: "ID",
|
||||||
align: 'right',
|
align: 'right',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
@@ -525,7 +537,7 @@
|
|||||||
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
|
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
|
{ 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.expireDate" }}', width: 100, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerMobileColumns = [
|
const innerMobileColumns = [
|
||||||
@@ -559,11 +571,13 @@
|
|||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
subSettings: {
|
subSettings: {
|
||||||
enable : false,
|
enable : false,
|
||||||
subURI : ''
|
subURI : '',
|
||||||
|
subJsonURI : '',
|
||||||
},
|
},
|
||||||
remarkModel: '-ieo',
|
remarkModel: '-ieo',
|
||||||
datepicker: 'gregorian',
|
datepicker: 'gregorian',
|
||||||
tgBotEnable: false,
|
tgBotEnable: false,
|
||||||
|
showAlert: false,
|
||||||
pageSize: 0,
|
pageSize: 0,
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
@@ -578,6 +592,7 @@
|
|||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.getOnlineUsers();
|
await this.getOnlineUsers();
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -604,7 +619,8 @@
|
|||||||
this.tgBotEnable = tgBotEnable;
|
this.tgBotEnable = tgBotEnable;
|
||||||
this.subSettings = {
|
this.subSettings = {
|
||||||
enable : subEnable,
|
enable : subEnable,
|
||||||
subURI: subURI
|
subURI: subURI,
|
||||||
|
subJsonURI: subJsonURI
|
||||||
};
|
};
|
||||||
this.pageSize = pageSize;
|
this.pageSize = pageSize;
|
||||||
this.remarkModel = remarkModel;
|
this.remarkModel = remarkModel;
|
||||||
@@ -642,8 +658,12 @@
|
|||||||
clientCount = clients.length;
|
clientCount = clients.length;
|
||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
if (client.enable) {
|
||||||
if(this.isClientOnline(client.email)) online.push(client.email);
|
active.push(client.email);
|
||||||
|
if (this.isClientOnline(client.email)) online.push(client.email);
|
||||||
|
} else {
|
||||||
|
deactive.push(client.email);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if (!client.enable) {
|
if (!client.enable) {
|
||||||
@@ -668,6 +688,7 @@
|
|||||||
online: online,
|
online: online,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
if (ObjectUtil.isEmpty(key)) {
|
if (ObjectUtil.isEmpty(key)) {
|
||||||
this.searchedInbounds = this.dbInbounds.slice();
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
@@ -731,6 +752,9 @@
|
|||||||
case "export":
|
case "export":
|
||||||
this.exportAllLinks();
|
this.exportAllLinks();
|
||||||
break;
|
break;
|
||||||
|
case "subs":
|
||||||
|
this.exportAllSubs();
|
||||||
|
break;
|
||||||
case "resetInbounds":
|
case "resetInbounds":
|
||||||
this.resetAllTraffic();
|
this.resetAllTraffic();
|
||||||
break;
|
break;
|
||||||
@@ -762,6 +786,9 @@
|
|||||||
case "export":
|
case "export":
|
||||||
this.inboundLinks(dbInbound.id);
|
this.inboundLinks(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
case "subs":
|
||||||
|
this.exportSubs(dbInbound.id);
|
||||||
|
break;
|
||||||
case "clipboard":
|
case "clipboard":
|
||||||
this.copyToClipboard(dbInbound.id);
|
this.copyToClipboard(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
@@ -811,7 +838,7 @@
|
|||||||
protocol: baseInbound.protocol,
|
protocol: baseInbound.protocol,
|
||||||
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
||||||
streamSettings: baseInbound.stream.toString(),
|
streamSettings: baseInbound.stream.toString(),
|
||||||
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
sniffing: baseInbound.sniffing.toString(),
|
||||||
};
|
};
|
||||||
await this.submit('/panel/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
@@ -821,9 +848,7 @@
|
|||||||
okText: '{{ i18n "pages.inbounds.create"}}',
|
okText: '{{ i18n "pages.inbounds.create"}}',
|
||||||
cancelText: '{{ i18n "close" }}',
|
cancelText: '{{ i18n "close" }}',
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
inModal.loading();
|
await this.addInbound(inbound, dbInbound, inModal);
|
||||||
await this.addInbound(inbound, dbInbound);
|
|
||||||
inModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
});
|
});
|
||||||
@@ -838,9 +863,7 @@
|
|||||||
inbound: inbound,
|
inbound: inbound,
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
inModal.loading();
|
|
||||||
await this.updateInbound(inbound, dbInbound);
|
await this.updateInbound(inbound, dbInbound);
|
||||||
inModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
});
|
});
|
||||||
@@ -860,7 +883,7 @@
|
|||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.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('/panel/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
@@ -879,7 +902,7 @@
|
|||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.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(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
||||||
},
|
},
|
||||||
@@ -890,9 +913,7 @@
|
|||||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientModal.loading();
|
await this.addClient(clients, dbInboundId, clientModal);
|
||||||
await this.addClient(clients, dbInboundId);
|
|
||||||
clientModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
});
|
});
|
||||||
@@ -904,9 +925,7 @@
|
|||||||
okText: '{{ i18n "pages.client.bulk"}}',
|
okText: '{{ i18n "pages.client.bulk"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientsBulkModal.loading();
|
await this.addClient(clients, dbInboundId, clientsBulkModal);
|
||||||
await this.addClient(clients, dbInboundId);
|
|
||||||
clientsBulkModal.close();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -935,19 +954,19 @@
|
|||||||
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async addClient(clients, dbInboundId) {
|
async addClient(clients, dbInboundId, modal) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + clients.toString() + ']}',
|
settings: '{"clients": [' + clients.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/addClient`, data);
|
await this.submit(`/panel/inbound/addClient`, data, modal);
|
||||||
},
|
},
|
||||||
async updateClient(client, dbInboundId, clientId) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() + ']}',
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
|
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
@@ -967,7 +986,7 @@
|
|||||||
},
|
},
|
||||||
delInbound(dbInboundId) {
|
delInbound(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
@@ -980,7 +999,7 @@
|
|||||||
clientId = this.getClientId(dbInbound.protocol, client);
|
clientId = this.getClientId(dbInbound.protocol, client);
|
||||||
if (confirmation){
|
if (confirmation){
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteClient"}}',
|
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
|
||||||
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
@@ -1050,8 +1069,8 @@
|
|||||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async submit(url, data) {
|
async submit(url, data, modal) {
|
||||||
const msg = await HttpUtil.postWithModal(url, data);
|
const msg = await HttpUtil.postWithModal(url, data, modal);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
await this.getDBInbounds();
|
await this.getDBInbounds();
|
||||||
}
|
}
|
||||||
@@ -1186,6 +1205,22 @@
|
|||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), 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() {
|
importInbound() {
|
||||||
promptModal.open({
|
promptModal.open({
|
||||||
title: '{{ i18n "pages.inbounds.importInbound" }}',
|
title: '{{ i18n "pages.inbounds.importInbound" }}',
|
||||||
@@ -1194,10 +1229,26 @@
|
|||||||
okText: '{{ i18n "pages.inbounds.import" }}',
|
okText: '{{ i18n "pages.inbounds.import" }}',
|
||||||
confirm: async (dbInboundText) => {
|
confirm: async (dbInboundText) => {
|
||||||
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
|
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
|
||||||
promptModal.close();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
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() {
|
exportAllLinks() {
|
||||||
let copyText = [];
|
let copyText = [];
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
@@ -1238,7 +1289,7 @@
|
|||||||
pagination(obj){
|
pagination(obj){
|
||||||
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
||||||
// Set page options based on object size
|
// Set page options based on object size
|
||||||
sizeOptions = []
|
sizeOptions = [];
|
||||||
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
||||||
sizeOptions.push(i.toString());
|
sizeOptions.push(i.toString());
|
||||||
}
|
}
|
||||||
@@ -1251,8 +1302,8 @@
|
|||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
pageSize: this.pageSize,
|
pageSize: this.pageSize,
|
||||||
pageSizeOptions: sizeOptions
|
pageSizeOptions: sizeOptions
|
||||||
}
|
};
|
||||||
return p
|
return p;
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
@@ -1266,6 +1317,9 @@
|
|||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
if (window.location.protocol !== "https:") {
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
this.loading();
|
this.loading();
|
||||||
@@ -1303,7 +1357,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
@@ -1313,6 +1366,5 @@
|
|||||||
{{template "inboundInfoModal"}}
|
{{template "inboundInfoModal"}}
|
||||||
{{template "clientsModal"}}
|
{{template "clientsModal"}}
|
||||||
{{template "clientsBulkModal"}}
|
{{template "clientsBulkModal"}}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -10,13 +10,11 @@
|
|||||||
margin-inline: 0.3rem;
|
margin-inline: 0.3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark h2 {
|
.ant-card-dark h2 {
|
||||||
color: hsla(0, 0%, 100%, .65);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -26,6 +24,15 @@
|
|||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||||
|
<transition name="list" appear>
|
||||||
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
</a-alert>
|
||||||
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
@@ -36,15 +43,15 @@
|
|||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
||||||
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
:percent="status.mem.percent"></a-progress>
|
:percent="status.mem.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
<b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -56,7 +63,7 @@
|
|||||||
:stroke-color="status.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
@@ -64,7 +71,7 @@
|
|||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
:percent="status.disk.percent"></a-progress>
|
:percent="status.disk.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
<b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -75,25 +82,25 @@
|
|||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
3X-UI <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
<b>3X-UI:</b>
|
||||||
Xray <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
<a rel="noopener" href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@Panel3xui</a-tag></a>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "menu.link" }}:
|
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
|
||||||
<a-tag :color="status.xray.color">[[ status.xray.state.toUpperCase() ]]</a-tag>
|
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]]
|
||||||
|
</a-tag>
|
||||||
<a-popover v-if="status.xray.state === State.Error"
|
<a-popover v-if="status.xray.state === State.Error"
|
||||||
:overlay-class-name="themeSwitcher.currentTheme">
|
:overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
||||||
@@ -106,137 +113,143 @@
|
|||||||
</a-popover>
|
</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="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;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
<b>{{ i18n "menu.link" }}:</b>
|
||||||
Xray
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
OS
|
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
||||||
|
<a-tag color="green">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.systemLoadDesc" }}
|
{{ i18n "pages.index.systemLoadDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "usage"}}:
|
<b>{{ i18n "usage"}}:</b>
|
||||||
RAM [[ sizeFormat(status.appStats.mem) ]] -
|
<a-tag color="green">
|
||||||
Threads [[ status.appStats.threads ]]
|
RAM: [[ sizeFormat(status.appStats.mem) ]]
|
||||||
</a-tooltip>
|
</a-tag>
|
||||||
|
<a-tag color="green">
|
||||||
|
Threads: [[ status.appStats.threads ]]
|
||||||
|
</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="global"></a-icon>
|
<a-tag>
|
||||||
IPv4:
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="global"></a-icon> IPv4
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv4 ]]
|
[[ status.publicIP.ipv4 ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-tag>
|
||||||
<a-col :span="12">
|
</a-col>
|
||||||
<a-icon type="global"></a-icon>
|
<a-col :span="12">
|
||||||
IPv6:
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="global"></a-icon> IPv6
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv6 ]]
|
[[ status.publicIP.ipv6 ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="swap"></a-icon>
|
<a-tag>
|
||||||
TCP: [[ status.tcpCount ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="swap"></a-icon>
|
<a-tag>
|
||||||
UDP: [[ status.udpCount ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-tag>
|
||||||
[[ sizeFormat(status.netIO.up) ]]/s
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="arrow-up"></a-icon>
|
||||||
|
Up: [[ sizeFormat(status.netIO.up) ]]/s
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.upSpeed" }}
|
{{ i18n "pages.index.upSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-down"></a-icon>
|
<a-tag>
|
||||||
[[ sizeFormat(status.netIO.down) ]]/s
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="arrow-down"></a-icon>
|
||||||
|
Down: [[ sizeFormat(status.netIO.down) ]]/s
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.downSpeed" }}
|
{{ i18n "pages.index.downSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-tag>
|
||||||
[[ sizeFormat(status.netTraffic.sent) ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalSent" }}
|
{{ i18n "pages.index.totalSent" }}
|
||||||
</template>
|
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-download"></a-icon>
|
<a-tag>
|
||||||
[[ sizeFormat(status.netTraffic.recv) ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="cloud-download"></a-icon>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalReceive" }}
|
{{ i18n "pages.index.totalReceive" }}
|
||||||
</template>
|
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -246,70 +259,66 @@
|
|||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
|
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
|
||||||
:closable="true" @ok="() => versionModal.visible = false"
|
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
|
||||||
:class="themeSwitcher.currentTheme"
|
|
||||||
footer="">
|
|
||||||
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
|
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
|
||||||
message='{{ i18n "pages.index.xraySwitchClickDesk" }}'
|
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
|
||||||
show-icon
|
|
||||||
></a-alert>
|
|
||||||
<template v-for="version, index in versionModal.versions">
|
<template v-for="version, index in versionModal.versions">
|
||||||
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'"
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'" style="margin-right: 12px; margin-bottom: 12px"
|
||||||
style="margin: 10px" @click="switchV2rayVersion(version)">
|
@click="switchV2rayVersion(version)">
|
||||||
[[ version ]]
|
[[ version ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible" title="Logs"
|
<a-modal id="log-modal" v-model="logModal.visible"
|
||||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
:closable="true" @cancel="() => logModal.visible = false"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="800px"
|
width="800px" footer="">
|
||||||
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 layout="inline">
|
||||||
<a-form-item label="Count">
|
<a-form-item>
|
||||||
<a-select v-model="logModal.rows"
|
<a-input-group compact>
|
||||||
style="width: 80px"
|
<a-select v-model="logModal.rows" style="width:70px;"
|
||||||
@change="openLogs()"
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="100">100</a-select-option>
|
||||||
<a-select-option value="100">100</a-select-option>
|
</a-select>
|
||||||
</a-select>
|
<a-select v-model="logModal.level" style="width:100px;"
|
||||||
</a-form-item>
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-form-item label="Log Level">
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
<a-select v-model="logModal.level"
|
<a-select-option value="info">Info</a-select-option>
|
||||||
style="width: 120px"
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
@change="openLogs()"
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select-option value="err">Error</a-select-option>
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
</a-select>
|
||||||
<a-select-option value="info">Info</a-select-option>
|
</a-input-group>
|
||||||
<a-select-option value="notice">Notice</a-select-option>
|
|
||||||
<a-select-option value="warning">Warning</a-select-option>
|
|
||||||
<a-select-option value="err">Error</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="SysLog">
|
|
||||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<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-form-item>
|
<a-form-item style="float: right;">
|
||||||
<a-button type="primary" style="margin-bottom: 10px;"
|
<a-button type="primary" icon="download"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" download="x-ui.log">
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs.join('\n'))" download="x-ui.log">
|
||||||
{{ i18n "download" }} x-ui.log
|
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.logs"></div>
|
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.formattedLogs"></div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||||
:closable="true" :class="themeSwitcher.currentTheme"
|
:closable="true" footer=""
|
||||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
:message="backupModal.description"
|
:message="backupModal.description"
|
||||||
show-icon
|
show-icon
|
||||||
@@ -432,14 +441,15 @@
|
|||||||
|
|
||||||
const logModal = {
|
const logModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
logs: '',
|
logs: [],
|
||||||
rows: 20,
|
rows: 20,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
syslog: false,
|
syslog: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
show(logs) {
|
show(logs) {
|
||||||
this.visible = true;
|
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) {
|
formatLogs(logs) {
|
||||||
let formattedLogs = '';
|
let formattedLogs = '';
|
||||||
@@ -517,6 +527,7 @@
|
|||||||
backupModal,
|
backupModal,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
|
showAlert: false,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning, tip = '{{ i18n "loading"}}') {
|
loading(spinning, tip = '{{ i18n "loading"}}') {
|
||||||
@@ -640,14 +651,14 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
let retries = 0;
|
if (window.location.protocol !== "https:") {
|
||||||
while (retries < 5) {
|
this.showAlert = true;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
try {
|
try {
|
||||||
await this.getStatus();
|
await this.getStatus();
|
||||||
retries = 0;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error occurred while fetching status:", e);
|
console.error(e);
|
||||||
retries++;
|
|
||||||
}
|
}
|
||||||
await PromiseUtil.sleep(2000);
|
await PromiseUtil.sleep(2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,28 +75,37 @@
|
|||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
|
<transition name="list" appear>
|
||||||
|
<a-alert type="error" v-if="confAlerts.length>0" style="margin: 10px 5px;"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
show-icon
|
||||||
|
closable
|
||||||
|
>
|
||||||
|
<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-space direction="vertical">
|
||||||
<a-card hoverable style="margin-bottom: .5rem;">
|
<a-card hoverable style="margin-bottom: .5rem; overflow-x: hidden;">
|
||||||
<a-row>
|
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
||||||
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="16">
|
<a-col :xs="24" :sm="14">
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<template>
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
<div>
|
</a-back-top>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
</a-back-top>
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
<a-alert type="warning" style="float: right; width: fit-content"
|
show-icon
|
||||||
message='{{ i18n "pages.settings.infoDesc" }}'
|
>
|
||||||
show-icon
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -129,7 +138,7 @@
|
|||||||
</a-list-item>
|
</a-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="1" :max="65531"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
||||||
@@ -164,7 +173,6 @@
|
|||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta title="Language" />
|
<a-list-item-meta title="Language" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select
|
||||||
@@ -189,16 +197,16 @@
|
|||||||
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider>
|
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider>
|
||||||
<a-form layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }">
|
<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-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
||||||
<a-input v-model="user.oldUsername"></a-input>
|
<a-input autocomplete="username" v-model="user.oldUsername"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
|
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
|
||||||
<password-input v-model="user.oldPassword"></password-input>
|
<password-input autocomplete="current-password" v-model="user.oldPassword"></password-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
|
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
|
||||||
<a-input v-model="user.newUsername"></a-input>
|
<a-input v-model="user.newUsername"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
|
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
|
||||||
<password-input v-model="user.newPassword"></password-input>
|
<password-input autocomplete="new-password" v-model="user.newPassword"></password-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||||
@@ -234,7 +242,6 @@
|
|||||||
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||||
<a-list item-layout="horizontal">
|
<a-list item-layout="horizontal">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
||||||
@@ -250,15 +257,13 @@
|
|||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta title="Telegram Bot Language" />
|
<a-list-item-meta title="Telegram Bot Language" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select
|
||||||
ref="selectBotLang"
|
ref="selectBotLang"
|
||||||
v-model="allSetting.tgLang"
|
v-model="allSetting.tgLang"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
style="width: 100%"
|
style="width: 100%">
|
||||||
>
|
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
@@ -277,14 +282,44 @@
|
|||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort" :min="1" :max="65531"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates" :min="1"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</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>
|
||||||
|
</a-list>
|
||||||
|
<a-collapse v-if="fragment">
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.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>
|
||||||
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@@ -310,11 +345,28 @@
|
|||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
user: {},
|
user: {},
|
||||||
lang: getLang(),
|
lang: getLang(),
|
||||||
showAlert: false,
|
|
||||||
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
||||||
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
||||||
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
|
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
|
||||||
remarkSample: '',
|
remarkSample: '',
|
||||||
|
defaultFragment: {
|
||||||
|
tag: "fragment",
|
||||||
|
protocol: "freedom",
|
||||||
|
settings: {
|
||||||
|
domainStrategy: "AsIs",
|
||||||
|
fragment: {
|
||||||
|
packets: "tlshello",
|
||||||
|
length: "100-200",
|
||||||
|
interval: "10-20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
streamSettings: {
|
||||||
|
sockopt: {
|
||||||
|
tcpKeepAliveIdle: 100,
|
||||||
|
tcpNoDelay: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
get remarkModel() {
|
get remarkModel() {
|
||||||
rm = this.allSetting.remarkModel;
|
rm = this.allSetting.remarkModel;
|
||||||
return rm.length>1 ? rm.substring(1).split('') : [];
|
return rm.length>1 ? rm.substring(1).split('') : [];
|
||||||
@@ -443,13 +495,68 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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() {
|
async mounted() {
|
||||||
await this.getAllSetting();
|
await this.getAllSetting();
|
||||||
while (true) {
|
while (true) {
|
||||||
await PromiseUtil.sleep(600);
|
await PromiseUtil.sleep(1000);
|
||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<a-divider style="margin: 0;">{{ i18n "pages.settings.toasts.modifySettings" }}</a-divider>
|
<a-divider style="margin: 0;">{{ i18n "pages.settings.toasts.modifySettings" }}</a-divider>
|
||||||
<a-collapse style="margin: 10px 0;">
|
<a-collapse style="margin: 10px 0;">
|
||||||
<a-collapse-panel header='WARP/WARP+ License Key'>
|
<a-collapse-panel header='WARP/WARP+ License Key'>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="License Key">
|
<a-form-item label="License Key">
|
||||||
<a-input v-model="warpPlus"></a-input>
|
<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-button @click="updateLicense(warpPlus)" :disabled="warpPlus.length<26" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.update" }}</a-button>
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
this.confirmLoading = loading;
|
this.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
async getData(){
|
async getData(){
|
||||||
@@ -140,6 +140,7 @@
|
|||||||
mtu: 1420,
|
mtu: 1420,
|
||||||
secretKey: warpModal.warpData.private_key,
|
secretKey: warpModal.warpData.private_key,
|
||||||
address: Object.values(config.interface.addresses),
|
address: Object.values(config.interface.addresses),
|
||||||
|
domainStrategy: 'ForceIP',
|
||||||
peers: [{
|
peers: [{
|
||||||
publicKey: peer.public_key,
|
publicKey: peer.public_key,
|
||||||
endpoint: peer.endpoint.host,
|
endpoint: peer.endpoint.host,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css?{{ .cur_ver }}">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
||||||
|
|
||||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/codemirror.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/codemirror.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
||||||
@@ -63,10 +63,19 @@
|
|||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
|
<transition name="list" appear>
|
||||||
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
</a-alert>
|
||||||
|
</transition>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-card hoverable style="margin-bottom: .5rem;">
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
<a-row>
|
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
||||||
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
||||||
@@ -80,7 +89,7 @@
|
|||||||
</a-popover>
|
</a-popover>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="16">
|
<a-col :xs="24" :sm="14">
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
@@ -99,7 +108,7 @@
|
|||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}'>
|
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}'>
|
||||||
<a-space direction="horizontal" style="padding: 20px 20px">
|
<a-space direction="horizontal" style="padding: 20px 20px">
|
||||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
<a-button type="danger" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-collapse>
|
<a-collapse>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
@@ -114,15 +123,12 @@
|
|||||||
<a-list-item>
|
<a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta
|
<a-list-item-meta title='{{ i18n "pages.xray.FreedomStrategy" }}'
|
||||||
title='{{ i18n "pages.xray.FreedomStrategy" }}'
|
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}' />
|
||||||
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select v-model="freedomStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="freedomStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
style="width: 100%">
|
style="width: 100%">
|
||||||
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -132,22 +138,67 @@
|
|||||||
</a-list-item>
|
</a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta
|
<a-list-item-meta title='{{ i18n "pages.xray.RoutingStrategy" }}'
|
||||||
title='{{ i18n "pages.xray.RoutingStrategy" }}'
|
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}' />
|
||||||
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}'/>
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
style="width: 100%">
|
||||||
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" style="text-align: center;">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
|
{{ i18n "pages.xray.logConfigsDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta title='{{ i18n "pages.xray.logLevel" }}'
|
||||||
|
description='{{ i18n "pages.xray.logLevelDesc" }}' />
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select v-model="setLogLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||||
v-model="routingStrategy"
|
<a-select-option v-for="s in logLevel" :value="s">[[ s ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-list-item>
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
|
||||||
|
description='{{ i18n "pages.xray.accessLogDesc" }}' />
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<template>
|
||||||
|
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||||
|
<a-select-option v-for="s in access" :key="s" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta title='{{ i18n "pages.xray.errorLog" }}'
|
||||||
|
description='{{ i18n "pages.xray.errorLogDesc" }}' />
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<template>
|
||||||
|
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||||
|
<a-select-option v-for="s in error" :key="s" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
@@ -227,8 +278,11 @@
|
|||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.MetaWARP"}}' desc='{{ i18n "pages.xray.MetaWARPDesc"}}' v-model="MetaWARPSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.AppleWARP"}}' desc='{{ i18n "pages.xray.AppleWARPDesc"}}' v-model="AppleWARPSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.RedditWARP"}}' desc='{{ i18n "pages.xray.RedditWARPDesc"}}' v-model="RedditWARPSettings"></setting-list-item>
|
||||||
</template>
|
</template>
|
||||||
<a-button v-else style="margin: 10px 0;" @click="showWarp">WARP {{ i18n "pages.xray.rules.outbound" }}</a-button>
|
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@@ -236,15 +290,19 @@
|
|||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
|
message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
|
||||||
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
||||||
<a-table :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="routingRuleData"
|
:data-source="routingRuleData"
|
||||||
:scroll="isMobile ? {} : { x: 1000 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
:indent-size="0"
|
:indent-size="0"
|
||||||
:style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'">
|
:style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'"
|
||||||
|
v-on:onSort="replaceRule">
|
||||||
<template slot="action" slot-scope="text, rule, index">
|
<template slot="action" slot-scope="text, rule, index">
|
||||||
[[ index+1 ]]
|
<table-sort-trigger :item-index="index"></table-sort-trigger>
|
||||||
|
<span class="ant-table-row-index">
|
||||||
|
[[ index+1 ]]
|
||||||
|
</span>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
@@ -293,6 +351,14 @@
|
|||||||
[[ rule.outboundTag ]]
|
[[ rule.outboundTag ]]
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="balancer" slot-scope="text, rule, index">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-if="rule.balancerTag">Balancer Tag: [[ rule.balancerTag ]]</p>
|
||||||
|
</template>
|
||||||
|
[[ rule.balancerTag ]]
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
<template slot="info" slot-scope="text, rule, index">
|
<template slot="info" slot-scope="text, rule, index">
|
||||||
<a-popover placement="bottomRight"
|
<a-popover placement="bottomRight"
|
||||||
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
||||||
@@ -331,6 +397,10 @@
|
|||||||
<td>Port</td>
|
<td>Port</td>
|
||||||
<td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
|
<td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-if="rule.balancerTag">
|
||||||
|
<td>Balancer Tag</td>
|
||||||
|
<td><a-tag color="blue">[[ rule.balancerTag ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
||||||
@@ -338,11 +408,27 @@
|
|||||||
</a-button>
|
</a-button>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table-sortable>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
<a-row>
|
||||||
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
|
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n
|
||||||
|
"pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
|
<a-button type="primary" icon="cloud" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||||
|
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"></a-icon>
|
||||||
|
<a-popconfirm placement="topRight" @confirm="resetOutboundTraffic(-1)"
|
||||||
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "reset"}}'
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: #008771' : 'color: #008771'"></a-icon>
|
||||||
|
<a-icon type="retweet" style="cursor: pointer;"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
<a-table :columns="outboundColumns" bordered
|
<a-table :columns="outboundColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="outboundData"
|
:data-source="outboundData"
|
||||||
@@ -355,10 +441,19 @@
|
|||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item v-if="index>0" @click="setFirstOutbound(index)">
|
||||||
|
<a-icon type="vertical-align-top"></a-icon>
|
||||||
|
{{ i18n "pages.xray.rules.first"}}
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item @click="editOutbound(index)">
|
<a-menu-item @click="editOutbound(index)">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="resetOutboundTraffic(index)">
|
||||||
|
<span>
|
||||||
|
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic"}}
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item @click="deleteOutbound(index)">
|
<a-menu-item @click="deleteOutbound(index)">
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
@@ -378,11 +473,14 @@
|
|||||||
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="traffic" slot-scope="text, outbound, index">
|
||||||
|
<a-tag color="green">[[ findOutboundTraffic(outbound) ]]</a-tag>
|
||||||
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-button type="primary" icon="plus" @click="addReverse()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
|
<a-button type="primary" icon="plus" @click="addReverse()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
|
||||||
<a-table :columns="reverseColumns" bordered
|
<a-table :columns="reverseColumns" bordered v-if="reverseData.length>0"
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="reverseData"
|
:data-source="reverseData"
|
||||||
:scroll="isMobile ? {} : { x: 200 }"
|
:scroll="isMobile ? {} : { x: 200 }"
|
||||||
@@ -408,6 +506,126 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-5" tab='{{ i18n "pages.xray.Balancers"}}' style="padding-top: 20px;" force-render="true">
|
||||||
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
|
message='{{ i18n "pages.xray.balancer.balancerDesc" }}' show-icon></a-alert>
|
||||||
|
<a-button type="primary" icon="plus" @click="addBalancer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.balancer.addBalancer"}}</a-button>
|
||||||
|
<a-table :columns="balancerColumns" bordered v-if="balancersData.length>0"
|
||||||
|
:row-key="r => r.key"
|
||||||
|
:data-source="balancersData"
|
||||||
|
:scroll="isMobile ? {} : { x: 200 }"
|
||||||
|
:pagination="false"
|
||||||
|
:indent-size="0"
|
||||||
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
||||||
|
<template slot="action" slot-scope="text, balancer, index">
|
||||||
|
[[ index+1 ]]
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editBalancer(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
{{ i18n "edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteBalancer(index)">
|
||||||
|
<span style="color: #FF4D4F">
|
||||||
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="strategy" slot-scope="text, balancer, index">
|
||||||
|
<a-tag style="margin:0;" v-if="balancer.strategy=='random'" color="purple">Random</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="balancer.strategy=='leastload'" color="green">Least Load</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="balancer.strategy=='leastping'" color="green">Least Ping</a-tag>
|
||||||
|
</template>
|
||||||
|
<template slot="selector" slot-scope="text, balancer, index">
|
||||||
|
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-6" tab='DNS' style="padding-top: 20px;" force-render="true">
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
||||||
|
<template v-if="enableDNS">
|
||||||
|
<setting-list-item type="text" title='{{ i18n "pages.xray.dns.tag" }}' desc='{{ i18n "pages.xray.dns.tagDesc" }}' v-model="dnsTag"></setting-list-item>
|
||||||
|
<a-list-item>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta title='{{ i18n "pages.xray.dns.strategy" }}' description='{{ i18n "pages.xray.dns.strategyDesc" }}' />
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-select
|
||||||
|
v-model="dnsStrategy"
|
||||||
|
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-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
<a-divider>DNS</a-divider>
|
||||||
|
<a-button type="primary" icon="plus" @click="addDNSServer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.dns.add" }}</a-button>
|
||||||
|
<a-table :columns="dnsColumns" bordered v-if="dnsServers.length>0"
|
||||||
|
:row-key="r => r.key"
|
||||||
|
:data-source="dnsServers"
|
||||||
|
:scroll="isMobile ? {} : { x: 200 }"
|
||||||
|
:pagination="false"
|
||||||
|
:indent-size="0"
|
||||||
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
||||||
|
<template slot="action" slot-scope="text,dns,index">
|
||||||
|
[[ index+1 ]]
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editDNSServer(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
{{ i18n "edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteDNSServer(index)">
|
||||||
|
<span style="color: #FF4D4F">
|
||||||
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="address" slot-scope="dns,index">
|
||||||
|
<span v-if="typeof dns == 'object'">[[ dns.address ]]</span>
|
||||||
|
<span v-else>[[ dns ]]</span>
|
||||||
|
</template>
|
||||||
|
<template slot="domain" slot-scope="dns,index">
|
||||||
|
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-divider>Fake DNS</a-divider>
|
||||||
|
<a-button type="primary" icon="plus" @click="addFakedns()" style="margin-bottom: 10px;">{{ i18n "pages.xray.fakedns.add" }}</a-button>
|
||||||
|
<a-table :columns="fakednsColumns" bordered v-if="fakeDns && fakeDns.length>0" :row-key="r => r.key"
|
||||||
|
:data-source="fakeDns" :scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0"
|
||||||
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
||||||
|
<template slot="action" slot-scope="text,fakedns,index">
|
||||||
|
[[ index+1 ]]
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
|
style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editFakedns(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
{{ i18n "edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteFakedns(index)">
|
||||||
|
<span style="color: #FF4D4F">
|
||||||
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
|
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
|
||||||
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
|
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
|
||||||
@@ -426,10 +644,14 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
|
{{template "component/sortableTable" .}}
|
||||||
{{template "component/setting"}}
|
{{template "component/setting"}}
|
||||||
{{template "ruleModal"}}
|
{{template "ruleModal"}}
|
||||||
{{template "outModal"}}
|
{{template "outModal"}}
|
||||||
{{template "reverseModal"}}
|
{{template "reverseModal"}}
|
||||||
|
{{template "balancerModal"}}
|
||||||
|
{{template "dnsModal"}}
|
||||||
|
{{template "fakednsModal"}}
|
||||||
{{template "warpModal"}}
|
{{template "warpModal"}}
|
||||||
<script>
|
<script>
|
||||||
const rulesColumns = [
|
const rulesColumns = [
|
||||||
@@ -446,9 +668,10 @@
|
|||||||
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
|
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
|
||||||
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
|
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
|
||||||
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
|
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
|
||||||
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true },
|
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 15, ellipsis: true },
|
||||||
{ title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
|
{ title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
|
||||||
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 },
|
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 15 },
|
||||||
|
{ title: '{{ i18n "pages.xray.rules.balancer"}}', dataIndex: 'balancerTag', align: 'center', width: 15 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const rulesMobileColumns = [
|
const rulesMobileColumns = [
|
||||||
@@ -463,6 +686,7 @@
|
|||||||
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
||||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', align: 'center', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const reverseColumns = [
|
const reverseColumns = [
|
||||||
@@ -472,6 +696,25 @@
|
|||||||
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const dnsColumns = [
|
||||||
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.dns.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
const fakednsColumns = [
|
||||||
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.fakedns.ipPool"}}', dataIndex: 'ipPool', align: 'center', width: 50 },
|
||||||
|
{ title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', align: 'center', width: 50 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const balancerColumns = [
|
||||||
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
|
{ title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' }},
|
||||||
|
{ title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' }},
|
||||||
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -483,8 +726,11 @@
|
|||||||
oldXraySetting: '',
|
oldXraySetting: '',
|
||||||
xraySetting: '',
|
xraySetting: '',
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
|
outboundsTraffic: [],
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
|
refreshing: false,
|
||||||
restartResult: '',
|
restartResult: '',
|
||||||
|
showAlert: false,
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
cm: null,
|
cm: null,
|
||||||
@@ -521,6 +767,9 @@
|
|||||||
protocol: "freedom"
|
protocol: "freedom"
|
||||||
},
|
},
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||||
|
logLevel: ["none" , "debug" , "info" , "warning", "error"],
|
||||||
|
access: [],
|
||||||
|
error: [],
|
||||||
settingsData: {
|
settingsData: {
|
||||||
protocols: {
|
protocols: {
|
||||||
bittorrent: ["bittorrent"],
|
bittorrent: ["bittorrent"],
|
||||||
@@ -547,6 +796,9 @@
|
|||||||
google: ["geosite:google"],
|
google: ["geosite:google"],
|
||||||
spotify: ["geosite:spotify"],
|
spotify: ["geosite:spotify"],
|
||||||
netflix: ["geosite:netflix"],
|
netflix: ["geosite:netflix"],
|
||||||
|
meta: ["geosite:meta"],
|
||||||
|
apple: ["geosite:apple"],
|
||||||
|
reddit: ["geosite:reddit"],
|
||||||
cn: [
|
cn: [
|
||||||
"geosite:cn",
|
"geosite:cn",
|
||||||
"regexp:.*\\.cn$"
|
"regexp:.*\\.cn$"
|
||||||
@@ -569,9 +821,11 @@
|
|||||||
familyProtectDNS: {
|
familyProtectDNS: {
|
||||||
"servers": [
|
"servers": [
|
||||||
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
|
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
|
||||||
"1.0.0.3"
|
"1.0.0.3",
|
||||||
|
"2606:4700:4700::1113",
|
||||||
|
"2606:4700:4700::1003"
|
||||||
],
|
],
|
||||||
"queryStrategy": "UseIPv4"
|
"queryStrategy": "UseIP"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -579,6 +833,12 @@
|
|||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
this.spinning = spinning;
|
this.spinning = spinning;
|
||||||
},
|
},
|
||||||
|
async getOutboundsTraffic() {
|
||||||
|
const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic");
|
||||||
|
if (msg.success) {
|
||||||
|
this.outboundsTraffic = msg.obj;
|
||||||
|
}
|
||||||
|
},
|
||||||
async getXraySetting() {
|
async getXraySetting() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/panel/xray/");
|
const msg = await HttpUtil.post("/panel/xray/");
|
||||||
@@ -612,10 +872,10 @@
|
|||||||
},
|
},
|
||||||
async getXrayResult() {
|
async getXrayResult() {
|
||||||
const msg = await HttpUtil.get("/panel/xray/getXrayResult");
|
const msg = await HttpUtil.get("/panel/xray/getXrayResult");
|
||||||
if(msg.success){
|
if (msg.success) {
|
||||||
this.restartResult=msg.obj;
|
this.restartResult=msg.obj;
|
||||||
if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj);
|
if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchUserSecret() {
|
async fetchUserSecret() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
@@ -653,9 +913,9 @@
|
|||||||
},
|
},
|
||||||
async toggleToken(value) {
|
async toggleToken(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
await this.getNewSecret();
|
await this.getNewSecret();
|
||||||
} else {
|
} else {
|
||||||
this.user.loginSecret = "";
|
this.user.loginSecret = "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async resetXrayConfigToDefault() {
|
async resetXrayConfigToDefault() {
|
||||||
@@ -744,7 +1004,7 @@
|
|||||||
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
||||||
this.cm.on('change',editor => {
|
this.cm.on('change',editor => {
|
||||||
value = editor.getValue();
|
value = editor.getValue();
|
||||||
if(this.isJsonString(value)){
|
if (this.isJsonString(value)) {
|
||||||
this[this.advSettings] = value;
|
this[this.advSettings] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -757,6 +1017,14 @@
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
findOutboundTraffic(o) {
|
||||||
|
for (const otraffic of this.outboundsTraffic) {
|
||||||
|
if (otraffic.tag == o.tag) {
|
||||||
|
return sizeFormat(otraffic.up) + ' / ' + sizeFormat(otraffic.down);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sizeFormat(0) + ' / ' + sizeFormat(0);
|
||||||
|
},
|
||||||
findOutboundAddress(o) {
|
findOutboundAddress(o) {
|
||||||
serverObj = null;
|
serverObj = null;
|
||||||
switch(o.protocol){
|
switch(o.protocol){
|
||||||
@@ -814,6 +1082,125 @@
|
|||||||
outbounds.splice(index,1);
|
outbounds.splice(index,1);
|
||||||
this.outboundSettings = JSON.stringify(outbounds);
|
this.outboundSettings = JSON.stringify(outbounds);
|
||||||
},
|
},
|
||||||
|
setFirstOutbound(index){
|
||||||
|
outbounds = this.templateSettings.outbounds;
|
||||||
|
outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
|
||||||
|
this.outboundSettings = JSON.stringify(outbounds);
|
||||||
|
},
|
||||||
|
async refreshOutboundTraffic() {
|
||||||
|
if (!this.refreshing) {
|
||||||
|
this.refreshing = true;
|
||||||
|
await this.getOutboundsTraffic();
|
||||||
|
|
||||||
|
data = []
|
||||||
|
if (this.templateSettings != null) {
|
||||||
|
this.templateSettings.outbounds.forEach((o, index) => {
|
||||||
|
data.push({'key': index, ...o});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outboundData = data;
|
||||||
|
this.refreshing = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async resetOutboundTraffic(index) {
|
||||||
|
let tag = "-alltags-";
|
||||||
|
if (index >= 0) {
|
||||||
|
tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
|
||||||
|
}
|
||||||
|
const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", { tag: tag });
|
||||||
|
if (msg.success) {
|
||||||
|
await this.refreshOutboundTraffic();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addBalancer() {
|
||||||
|
balancerModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
||||||
|
okText: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
||||||
|
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
||||||
|
balancer: {
|
||||||
|
tag: '',
|
||||||
|
strategy: 'random',
|
||||||
|
selector: []
|
||||||
|
},
|
||||||
|
confirm: (balancer) => {
|
||||||
|
balancerModal.loading();
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
if (newTemplateSettings.routing.balancers == undefined) {
|
||||||
|
newTemplateSettings.routing.balancers = [];
|
||||||
|
}
|
||||||
|
let tmpBalancer = {
|
||||||
|
'tag': balancer.tag,
|
||||||
|
'selector': balancer.selector
|
||||||
|
};
|
||||||
|
if (balancer.strategy === 'roundRobin' || balancer.strategy === 'leastload' || balancer.strategy === 'leastping') {
|
||||||
|
tmpBalancer.strategy = {
|
||||||
|
'type': balancer.strategy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
balancerModal.close();
|
||||||
|
},
|
||||||
|
isEdit: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editBalancer(index) {
|
||||||
|
const oldTag = this.balancersData[index].tag;
|
||||||
|
balancerModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.balancer.editBalancer"}}',
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
||||||
|
balancer: this.balancersData[index],
|
||||||
|
confirm: (balancer) => {
|
||||||
|
balancerModal.loading();
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
|
||||||
|
let tmpBalancer = {
|
||||||
|
'tag': balancer.tag,
|
||||||
|
'selector': balancer.selector
|
||||||
|
};
|
||||||
|
if (balancer.strategy === 'roundRobin' || balancer.strategy === 'leastload' || balancer.strategy === 'leastping') {
|
||||||
|
tmpBalancer.strategy = {
|
||||||
|
'type': balancer.strategy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
newTemplateSettings.routing.balancers[index] = tmpBalancer;
|
||||||
|
// change edited tag if used in rule section
|
||||||
|
if (oldTag != balancer.tag) {
|
||||||
|
newTemplateSettings.routing.rules.forEach((rule) => {
|
||||||
|
if (rule.balancerTag && rule.balancerTag == oldTag) {
|
||||||
|
rule.balancerTag = balancer.tag;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
balancerModal.close();
|
||||||
|
},
|
||||||
|
isEdit: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteBalancer(index) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
|
||||||
|
// Remove from balancers
|
||||||
|
const removedBalancer = this.balancersData.splice(index, 1)[0];
|
||||||
|
|
||||||
|
// Remove from settings
|
||||||
|
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
|
||||||
|
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
||||||
|
|
||||||
|
// Remove related routing rules
|
||||||
|
let rules = newTemplateSettings.routing.rules.filter((r) => !r.balancerTag || r.balancerTag !== removedBalancer.tag);
|
||||||
|
newTemplateSettings.routing.rules = rules;
|
||||||
|
|
||||||
|
// Update balancers property to an empty array if there are no more balancers
|
||||||
|
if (newTemplateSettings.routing.balancers.length === 0) {
|
||||||
|
delete newTemplateSettings.routing.balancers;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
addReverse(){
|
addReverse(){
|
||||||
reverseModal.show({
|
reverseModal.show({
|
||||||
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
||||||
@@ -896,9 +1283,69 @@
|
|||||||
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
|
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
|
||||||
}
|
}
|
||||||
newTemplateSettings.routing.rules = newRules;
|
newTemplateSettings.routing.rules = newRules;
|
||||||
|
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
|
addDNSServer(){
|
||||||
|
dnsModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.dns.add" }}',
|
||||||
|
confirm: (dnsServer) => {
|
||||||
|
dnsServers = this.dnsServers;
|
||||||
|
dnsServers.push(dnsServer);
|
||||||
|
this.dnsServers = dnsServers;
|
||||||
|
dnsModal.close();
|
||||||
|
},
|
||||||
|
isEdit: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editDNSServer(index){
|
||||||
|
dnsModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.dns.edit" }} #' + (index+1),
|
||||||
|
dnsServer: this.dnsServers[index],
|
||||||
|
confirm: (dnsServer) => {
|
||||||
|
dnsServers = this.dnsServers;
|
||||||
|
dnsServers[index] = dnsServer;
|
||||||
|
this.dnsServers = dnsServers;
|
||||||
|
dnsModal.close();
|
||||||
|
},
|
||||||
|
isEdit: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteDNSServer(index){
|
||||||
|
newDnsServers = this.dnsServers;
|
||||||
|
newDnsServers.splice(index,1);
|
||||||
|
this.dnsServers = newDnsServers;
|
||||||
|
},
|
||||||
|
addFakedns() {
|
||||||
|
fakednsModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.fakedns.add" }}',
|
||||||
|
confirm: (item) => {
|
||||||
|
fakeDns = this.fakeDns?? [];
|
||||||
|
fakeDns.push(item);
|
||||||
|
this.fakeDns = fakeDns;
|
||||||
|
fakednsModal.close();
|
||||||
|
},
|
||||||
|
isEdit: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editFakedns(index){
|
||||||
|
fakednsModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.fakedns.edit" }} #' + (index+1),
|
||||||
|
fakeDns: this.fakeDns[index],
|
||||||
|
confirm: (item) => {
|
||||||
|
fakeDns = this.fakeDns;
|
||||||
|
fakeDns[index] = item;
|
||||||
|
this.fakeDns = fakeDns;
|
||||||
|
fakednsModal.close();
|
||||||
|
},
|
||||||
|
isEdit: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteFakedns(index){
|
||||||
|
fakeDns = this.fakeDns;
|
||||||
|
fakeDns.splice(index,1);
|
||||||
|
this.fakeDns = fakeDns;
|
||||||
|
},
|
||||||
addRule(){
|
addRule(){
|
||||||
ruleModal.show({
|
ruleModal.show({
|
||||||
title: '{{ i18n "pages.xray.rules.add"}}',
|
title: '{{ i18n "pages.xray.rules.add"}}',
|
||||||
@@ -945,8 +1392,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
if (window.location.protocol !== "https:") {
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
await this.getXraySetting();
|
await this.getXraySetting();
|
||||||
await this.getXrayResult();
|
await this.getXrayResult();
|
||||||
|
await this.getOutboundsTraffic();
|
||||||
while (true) {
|
while (true) {
|
||||||
await PromiseUtil.sleep(800);
|
await PromiseUtil.sleep(800);
|
||||||
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
||||||
@@ -954,8 +1405,31 @@
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
templateSettings: {
|
templateSettings: {
|
||||||
get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; },
|
get: function () {
|
||||||
set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); },
|
const parsedSettings = this.xraySetting ? JSON.parse(this.xraySetting) : null;
|
||||||
|
let accessLogPath = "./access.log";
|
||||||
|
let errorLogPath = "./error.log";
|
||||||
|
|
||||||
|
if (parsedSettings && parsedSettings.log) {
|
||||||
|
if (parsedSettings.log.access && parsedSettings.log.access !== "none") {
|
||||||
|
accessLogPath = parsedSettings.log.access;
|
||||||
|
}
|
||||||
|
if (parsedSettings.log.error && parsedSettings.log.error !== "none") {
|
||||||
|
errorLogPath = parsedSettings.log.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.access = ["none", accessLogPath];
|
||||||
|
this.error = ["none", errorLogPath];
|
||||||
|
return parsedSettings;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue && newValue.log) {
|
||||||
|
this.xraySetting = JSON.stringify(newValue, null, 2);
|
||||||
|
this.access = ["none", newValue.log.access || "./access.log"];
|
||||||
|
this.error = ["none", newValue.log.error || "./error.log"];
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
inboundSettings: {
|
inboundSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
||||||
@@ -1002,6 +1476,27 @@
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
balancersData: {
|
||||||
|
get: function () {
|
||||||
|
data = []
|
||||||
|
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
|
||||||
|
this.templateSettings.routing.balancers.forEach((o, index) => {
|
||||||
|
let strategy = "random"
|
||||||
|
if (o.strategy && (o.strategy.type == "roundRobin" || o.strategy.type == "leastload" || o.strategy.type == "leastping")) {
|
||||||
|
strategy = o.strategy.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
'key': index,
|
||||||
|
'tag': o.tag ? o.tag : "",
|
||||||
|
'strategy': strategy,
|
||||||
|
'selector': o.selector ? o.selector : []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
routingRuleSettings: {
|
routingRuleSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
@@ -1063,6 +1558,39 @@
|
|||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setLogLevel: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
|
||||||
|
return this.templateSettings.log.loglevel;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.log.loglevel = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accessLog: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "";
|
||||||
|
return this.templateSettings.log.access;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.log.access = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
errorLog: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
|
||||||
|
return this.templateSettings.log.error;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.log.error = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
blockedIPs: {
|
blockedIPs: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
||||||
@@ -1185,14 +1713,14 @@
|
|||||||
familyProtectSettings: {
|
familyProtectSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
||||||
return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
|
return doAllItemsExist(this.settingsData.familyProtectDNS.servers, this.templateSettings.dns.servers);
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
||||||
} else {
|
} else {
|
||||||
delete newTemplateSettings.dns;
|
newTemplateSettings.dns.servers = newTemplateSettings.dns?.servers?.filter(data => !this.settingsData.familyProtectDNS.servers.includes(data))
|
||||||
}
|
}
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
@@ -1454,6 +1982,42 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MetaWARPSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.meta, this.warpDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.meta];
|
||||||
|
} else {
|
||||||
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.meta.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppleWARPSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.apple, this.warpDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.apple];
|
||||||
|
} else {
|
||||||
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.apple.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RedditWARPSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.reddit, this.warpDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.reddit];
|
||||||
|
} else {
|
||||||
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.reddit.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
SpotifyWARPSettings: {
|
SpotifyWARPSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
|
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
|
||||||
@@ -1466,6 +2030,62 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
enableDNS: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateSettings ? this.templateSettings.dns != null : false;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
if (newValue) {
|
||||||
|
newTemplateSettings.dns = { servers: [], queryStrategy: "UseIP", tag: "dns_inbound" };
|
||||||
|
newTemplateSettings.fakedns = null;
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.dns;
|
||||||
|
delete newTemplateSettings.fakedns;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dnsTag: {
|
||||||
|
get: function () {
|
||||||
|
return this.enableDNS ? this.templateSettings.dns.tag : "";
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.dns.tag = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dnsStrategy: {
|
||||||
|
get: function () {
|
||||||
|
return this.enableDNS ? this.templateSettings.dns.queryStrategy : null;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.dns.queryStrategy = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dnsServers: {
|
||||||
|
get: function () { return this.enableDNS ? this.templateSettings.dns.servers : []; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.dns.servers = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fakeDns: {
|
||||||
|
get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
if (this.enableDNS) {
|
||||||
|
newTemplateSettings.fakedns = newValue.length > 0 ? newValue : null;
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.fakedns;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
115
web/html/xui/xray_balancer_modal.html
Normal file
115
web/html/xui/xray_balancer_modal.html
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
{{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:8} }" :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}}
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
outModal.visible = false;
|
outModal.visible = false;
|
||||||
outModal.loading(false);
|
outModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
outModal.confirmLoading = loading;
|
outModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
check(){
|
check(){
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
||||||
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
@@ -114,13 +113,14 @@
|
|||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
this.inboundTags.push(...app.inboundTags);
|
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);
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
reverseModal.visible = false;
|
reverseModal.visible = false;
|
||||||
reverseModal.loading(false);
|
reverseModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
reverseModal.confirmLoading = loading;
|
reverseModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -132,8 +132,6 @@
|
|||||||
reverseModal: reverseModal,
|
reverseModal: reverseModal,
|
||||||
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
||||||
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='Domain Matcher'>
|
<a-form-item label='Domain Matcher'>
|
||||||
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<a-input v-model.trim="ruleModal.rule.source"></a-input>
|
<a-input v-model.trim="ruleModal.rule.source"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">Source Port
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
@@ -107,6 +107,19 @@
|
|||||||
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</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>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -133,11 +146,12 @@
|
|||||||
protocol: [],
|
protocol: [],
|
||||||
attrs: [],
|
attrs: [],
|
||||||
outboundTag: "",
|
outboundTag: "",
|
||||||
|
balancerTag: "",
|
||||||
},
|
},
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
outboundTags: [],
|
outboundTags: [],
|
||||||
users: [],
|
users: [],
|
||||||
balancerTag: [],
|
balancerTags: [],
|
||||||
ok() {
|
ok() {
|
||||||
newRule = ruleModal.getResult();
|
newRule = ruleModal.getResult();
|
||||||
ObjectUtil.execute(ruleModal.confirm, newRule);
|
ObjectUtil.execute(ruleModal.confirm, newRule);
|
||||||
@@ -160,6 +174,7 @@
|
|||||||
this.rule.protocol = rule.protocol;
|
this.rule.protocol = rule.protocol;
|
||||||
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
||||||
this.rule.outboundTag = rule.outboundTag;
|
this.rule.outboundTag = rule.outboundTag;
|
||||||
|
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : "";
|
||||||
} else {
|
} else {
|
||||||
this.rule = {
|
this.rule = {
|
||||||
domainMatcher: "",
|
domainMatcher: "",
|
||||||
@@ -174,24 +189,30 @@
|
|||||||
protocol: [],
|
protocol: [],
|
||||||
attrs: [],
|
attrs: [],
|
||||||
outboundTag: "",
|
outboundTag: "",
|
||||||
|
balancerTag: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
this.inboundTags.push(...app.inboundTags);
|
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){
|
||||||
if(app.templateSettings.reverse.bridges) {
|
if(app.templateSettings.reverse.bridges) {
|
||||||
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
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.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() {
|
close() {
|
||||||
ruleModal.visible = false;
|
ruleModal.visible = false;
|
||||||
ruleModal.loading(false);
|
ruleModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
ruleModal.confirmLoading = loading;
|
ruleModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
getResult() {
|
getResult() {
|
||||||
@@ -210,7 +231,8 @@
|
|||||||
rule.inboundTag = value.inboundTag;
|
rule.inboundTag = value.inboundTag;
|
||||||
rule.protocol = value.protocol;
|
rule.protocol = value.protocol;
|
||||||
rule.attrs = Object.fromEntries(value.attrs);
|
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)) {
|
for (const [key, value] of Object.entries(rule)) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -16,18 +18,12 @@ import (
|
|||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckClientIpJob struct{}
|
type CheckClientIpJob struct {
|
||||||
|
lastClear int64
|
||||||
|
disAllowedIps []string
|
||||||
|
}
|
||||||
|
|
||||||
var job *CheckClientIpJob
|
var job *CheckClientIpJob
|
||||||
var disAllowedIps []string
|
|
||||||
var ipFiles = []string{
|
|
||||||
xray.GetIPLimitLogPath(),
|
|
||||||
xray.GetIPLimitPrevLogPath(),
|
|
||||||
xray.GetIPLimitBannedLogPath(),
|
|
||||||
xray.GetIPLimitBannedPrevLogPath(),
|
|
||||||
xray.GetAccessPersistentLogPath(),
|
|
||||||
xray.GetAccessPersistentPrevLogPath(),
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCheckClientIpJob() *CheckClientIpJob {
|
func NewCheckClientIpJob() *CheckClientIpJob {
|
||||||
job = new(CheckClientIpJob)
|
job = new(CheckClientIpJob)
|
||||||
@@ -35,19 +31,50 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
func (j *CheckClientIpJob) Run() {
|
||||||
|
if j.lastClear == 0 {
|
||||||
// create files required for iplimit if not exists
|
j.lastClear = time.Now().Unix()
|
||||||
for i := 0; i < len(ipFiles); i++ {
|
|
||||||
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
|
||||||
j.checkError(err)
|
|
||||||
defer file.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for limit ip
|
shouldClearAccessLog := false
|
||||||
|
f2bInstalled := j.checkFail2BanInstalled()
|
||||||
|
isAccessLogAvailable := j.checkAccessLogAvailable(f2bInstalled)
|
||||||
|
|
||||||
if j.hasLimitIp() {
|
if j.hasLimitIp() {
|
||||||
j.checkFail2BanInstalled()
|
if f2bInstalled && isAccessLogAvailable {
|
||||||
j.processLogFile()
|
shouldClearAccessLog = j.processLogFile()
|
||||||
|
} else {
|
||||||
|
if !f2bInstalled {
|
||||||
|
logger.Warning("fail2ban is not installed. IP limiting may not work properly.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldClearAccessLog || isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600 {
|
||||||
|
j.clearAccessLog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *CheckClientIpJob) clearAccessLog() {
|
||||||
|
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||||
|
j.checkError(err)
|
||||||
|
|
||||||
|
// reopen the access log file for reading
|
||||||
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
|
file, err := os.Open(accessLogPath)
|
||||||
|
j.checkError(err)
|
||||||
|
|
||||||
|
// copy access log content to persistent file
|
||||||
|
_, err = io.Copy(logAccessP, file)
|
||||||
|
j.checkError(err)
|
||||||
|
|
||||||
|
// close the file after copying content
|
||||||
|
logAccessP.Close()
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
// clean access log
|
||||||
|
err = os.Truncate(accessLogPath, 0)
|
||||||
|
j.checkError(err)
|
||||||
|
j.lastClear = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) hasLimitIp() bool {
|
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||||
@@ -79,36 +106,32 @@ func (j *CheckClientIpJob) hasLimitIp() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) checkFail2BanInstalled() {
|
func (j *CheckClientIpJob) checkFail2BanInstalled() bool {
|
||||||
cmd := "fail2ban-client"
|
cmd := "fail2ban-client"
|
||||||
args := []string{"-h"}
|
args := []string{"-h"}
|
||||||
|
|
||||||
err := exec.Command(cmd, args...).Run()
|
err := exec.Command(cmd, args...).Run()
|
||||||
if err != nil {
|
return err == nil
|
||||||
logger.Warning("fail2ban is not installed. IP limiting may not work properly.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) processLogFile() {
|
func (j *CheckClientIpJob) processLogFile() bool {
|
||||||
accessLogPath := xray.GetAccessLogPath()
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
if accessLogPath == "" {
|
|
||||||
logger.Warning("access.log doesn't exist in your config.json")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := os.ReadFile(accessLogPath)
|
file, err := os.Open(accessLogPath)
|
||||||
InboundClientIps := make(map[string][]string)
|
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
lines := strings.Split(string(data), "\n")
|
InboundClientIps := make(map[string][]string)
|
||||||
for _, line := range lines {
|
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
ipRegx, _ := regexp.Compile(`(\d+\.\d+\.\d+\.\d+).* accepted`)
|
||||||
emailRegx, _ := regexp.Compile(`email:.+`)
|
emailRegx, _ := regexp.Compile(`email:.+`)
|
||||||
|
|
||||||
matchesIp := ipRegx.FindString(line)
|
matches := ipRegx.FindStringSubmatch(line)
|
||||||
if len(matchesIp) > 0 {
|
if len(matches) > 1 {
|
||||||
ip := string(matchesIp)
|
ip := matches[1]
|
||||||
if ip == "127.0.0.1" || ip == "1.1.1.1" {
|
if ip == "127.0.0.1" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,14 +146,15 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disAllowedIps = []string{}
|
j.checkError(scanner.Err())
|
||||||
|
file.Close()
|
||||||
|
|
||||||
shouldCleanLog := false
|
shouldCleanLog := false
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
for clientEmail, ips := range InboundClientIps {
|
||||||
@@ -141,28 +165,28 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
} else {
|
} else {
|
||||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
return shouldCleanLog
|
||||||
time.Sleep(time.Second * 2)
|
}
|
||||||
|
|
||||||
if shouldCleanLog {
|
func (j *CheckClientIpJob) checkAccessLogAvailable(doWarning bool) bool {
|
||||||
// copy access log to persistent file
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
isAvailable := true
|
||||||
j.checkError(err)
|
warningMsg := ""
|
||||||
input, err := os.ReadFile(accessLogPath)
|
// access log is not available if it is set to 'none' or an empty string
|
||||||
j.checkError(err)
|
switch accessLogPath {
|
||||||
if _, err := logAccessP.Write(input); err != nil {
|
case "none":
|
||||||
j.checkError(err)
|
warningMsg = "Access log is set to 'none', check your Xray Configs"
|
||||||
}
|
isAvailable = false
|
||||||
defer logAccessP.Close()
|
case "":
|
||||||
|
warningMsg = "Access log doesn't exist in your Xray Configs"
|
||||||
// clean access log
|
isAvailable = false
|
||||||
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
|
||||||
j.checkError(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if doWarning && warningMsg != "" {
|
||||||
|
logger.Warning(warningMsg)
|
||||||
|
}
|
||||||
|
return isAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) checkError(e error) {
|
func (j *CheckClientIpJob) checkError(e error) {
|
||||||
@@ -237,9 +261,10 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
|||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
clients := settings["clients"]
|
clients := settings["clients"]
|
||||||
shouldCleanLog := false
|
shouldCleanLog := false
|
||||||
|
j.disAllowedIps = []string{}
|
||||||
|
|
||||||
// create iplimit log file channel
|
// create iplimit log file channel
|
||||||
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("failed to create or open ip limit log file: %s", err)
|
logger.Errorf("failed to create or open ip limit log file: %s", err)
|
||||||
}
|
}
|
||||||
@@ -255,7 +280,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
|||||||
shouldCleanLog = true
|
shouldCleanLog = true
|
||||||
|
|
||||||
if limitIp < len(ips) && inbound.Enable {
|
if limitIp < len(ips) && inbound.Enable {
|
||||||
disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
|
j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...)
|
||||||
for i := limitIp; i < len(ips); i++ {
|
for i := limitIp; i < len(ips); i++ {
|
||||||
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
|
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
|
||||||
}
|
}
|
||||||
@@ -263,14 +288,17 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Debug("disAllowedIps ", disAllowedIps)
|
|
||||||
sort.Strings(disAllowedIps)
|
sort.Strings(j.disAllowedIps)
|
||||||
|
|
||||||
|
if len(j.disAllowedIps) > 0 {
|
||||||
|
logger.Debug("disAllowedIps ", j.disAllowedIps)
|
||||||
|
}
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Save(inboundClientIps).Error
|
err = db.Save(inboundClientIps).Error
|
||||||
if err != nil {
|
j.checkError(err)
|
||||||
return shouldCleanLog
|
|
||||||
}
|
|
||||||
return shouldCleanLog
|
return shouldCleanLog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package job
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (j *CheckXrayRunningJob) Run() {
|
|||||||
j.checkTime = 0
|
j.checkTime = 0
|
||||||
} else {
|
} else {
|
||||||
j.checkTime++
|
j.checkTime++
|
||||||
//only restart if it's down 2 times in a row
|
// only restart if it's down 2 times in a row
|
||||||
if j.checkTime > 1 {
|
if j.checkTime > 1 {
|
||||||
err := j.xrayService.RestartXray(false)
|
err := j.xrayService.RestartXray(false)
|
||||||
j.checkTime = 0
|
j.checkTime = 0
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
@@ -15,7 +17,7 @@ func NewClearLogsJob() *ClearLogsJob {
|
|||||||
// Here Run is an interface method of the Job interface
|
// Here Run is an interface method of the Job interface
|
||||||
func (j *ClearLogsJob) Run() {
|
func (j *ClearLogsJob) Run() {
|
||||||
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
|
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
|
||||||
logFilesPrev := []string{xray.GetIPLimitPrevLogPath(), xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
|
logFilesPrev := []string{xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
|
||||||
|
|
||||||
// clear old previous logs
|
// clear old previous logs
|
||||||
for i := 0; i < len(logFilesPrev); i++ {
|
for i := 0; i < len(logFilesPrev); i++ {
|
||||||
@@ -26,25 +28,28 @@ func (j *ClearLogsJob) Run() {
|
|||||||
|
|
||||||
// clear log files and copy to previous logs
|
// clear log files and copy to previous logs
|
||||||
for i := 0; i < len(logFiles); i++ {
|
for i := 0; i < len(logFiles); i++ {
|
||||||
|
if i > 0 {
|
||||||
// copy to previous logs
|
// copy to previous logs
|
||||||
logFilePrev, err := os.OpenFile(logFilesPrev[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
logFilePrev, err := os.OpenFile(logFilesPrev[i-1], os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("clear logs job err:", err)
|
logger.Warning("clear logs job err:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logFile, err := os.Open(logFiles[i])
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("clear logs job err:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(logFilePrev, logFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("clear logs job err:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logFile.Close()
|
||||||
|
logFilePrev.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
logFile, err := os.ReadFile(logFiles[i])
|
err := os.Truncate(logFiles[i], 0)
|
||||||
if err != nil {
|
|
||||||
logger.Warning("clear logs job err:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = logFilePrev.Write(logFile)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("clear logs job err:", err)
|
|
||||||
}
|
|
||||||
defer logFilePrev.Close()
|
|
||||||
|
|
||||||
err = os.Truncate(logFiles[i], 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("clear logs job err:", err)
|
logger.Warning("clear logs job err:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type XrayTrafficJob struct {
|
type XrayTrafficJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
|
outboundService service.OutboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXrayTrafficJob() *XrayTrafficJob {
|
func NewXrayTrafficJob() *XrayTrafficJob {
|
||||||
@@ -24,12 +25,15 @@ func (j *XrayTrafficJob) Run() {
|
|||||||
logger.Warning("get xray traffic failed:", err)
|
logger.Warning("get xray traffic failed:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("add traffic failed:", err)
|
logger.Warning("add inbound traffic failed:", err)
|
||||||
}
|
}
|
||||||
if needRestart {
|
err, needRestart1 := j.outboundService.AddTraffic(traffics, clientTraffics)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("add outbound traffic failed:", err)
|
||||||
|
}
|
||||||
|
if needRestart0 || needRestart1 {
|
||||||
j.xrayService.SetToNeedRestart()
|
j.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -12,9 +13,11 @@ import (
|
|||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
var i18nBundle *i18n.Bundle
|
var (
|
||||||
var LocalizerWeb *i18n.Localizer
|
i18nBundle *i18n.Bundle
|
||||||
var LocalizerBot *i18n.Localizer
|
LocalizerWeb *i18n.Localizer
|
||||||
|
LocalizerBot *i18n.Localizer
|
||||||
|
)
|
||||||
|
|
||||||
type I18nType string
|
type I18nType string
|
||||||
|
|
||||||
@@ -79,7 +82,6 @@ func I18n(i18nType I18nType, key string, params ...string) string {
|
|||||||
MessageID: key,
|
MessageID: key,
|
||||||
TemplateData: templateData,
|
TemplateData: templateData,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to localize message: %v", err)
|
logger.Errorf("Failed to localize message: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@@ -135,7 +137,6 @@ func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
|
|||||||
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
"access": "none",
|
||||||
"error": "./error.log"
|
"dnsLog": false,
|
||||||
|
"error": "./error.log",
|
||||||
|
"loglevel": "warning"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
@@ -24,8 +26,11 @@
|
|||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
|
"tag": "direct",
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {}
|
"settings": {
|
||||||
|
"domainStrategy": "UseIP"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "blocked",
|
"tag": "blocked",
|
||||||
@@ -42,11 +47,13 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"statsInboundDownlink": true,
|
"statsInboundDownlink": true,
|
||||||
"statsInboundUplink": true
|
"statsInboundUplink": true,
|
||||||
|
"statsOutboundDownlink": true,
|
||||||
|
"statsOutboundUplink": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "AsIs",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
@@ -90,7 +91,6 @@ func (s *InboundService) getAllEmails() ([]string, error) {
|
|||||||
FROM inbounds,
|
FROM inbounds,
|
||||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
`).Scan(&emails).Error
|
`).Scan(&emails).Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -312,12 +312,11 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
oldInbound.StreamSettings = inbound.StreamSettings
|
oldInbound.StreamSettings = inbound.StreamSettings
|
||||||
oldInbound.Sniffing = inbound.Sniffing
|
oldInbound.Sniffing = inbound.Sniffing
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||||
oldInbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
|
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
} else {
|
} else {
|
||||||
oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
needRestart := false
|
needRestart := false
|
||||||
s.xrayApi.Init(p.GetAPIPort())
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
if s.xrayApi.DelInbound(tag) == nil {
|
if s.xrayApi.DelInbound(tag) == nil {
|
||||||
@@ -509,6 +508,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
|
settings["clients"] = newClients
|
||||||
newSettings, err := json.MarshalIndent(settings, "", " ")
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -570,15 +573,19 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldEmail := ""
|
oldEmail := ""
|
||||||
|
newClientId := ""
|
||||||
clientIndex := 0
|
clientIndex := 0
|
||||||
for index, oldClient := range oldClients {
|
for index, oldClient := range oldClients {
|
||||||
oldClientId := ""
|
oldClientId := ""
|
||||||
if oldInbound.Protocol == "trojan" {
|
if oldInbound.Protocol == "trojan" {
|
||||||
oldClientId = oldClient.Password
|
oldClientId = oldClient.Password
|
||||||
|
newClientId = clients[0].Password
|
||||||
} else if oldInbound.Protocol == "shadowsocks" {
|
} else if oldInbound.Protocol == "shadowsocks" {
|
||||||
oldClientId = oldClient.Email
|
oldClientId = oldClient.Email
|
||||||
|
newClientId = clients[0].Email
|
||||||
} else {
|
} else {
|
||||||
oldClientId = oldClient.ID
|
oldClientId = oldClient.ID
|
||||||
|
newClientId = clients[0].ID
|
||||||
}
|
}
|
||||||
if clientId == oldClientId {
|
if clientId == oldClientId {
|
||||||
oldEmail = oldClient.Email
|
oldEmail = oldClient.Email
|
||||||
@@ -587,6 +594,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 {
|
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
||||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -966,7 +978,7 @@ func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error
|
|||||||
s.xrayApi.Init(p.GetAPIPort())
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
err1 := s.xrayApi.DelInbound(tag)
|
err1 := s.xrayApi.DelInbound(tag)
|
||||||
if err == nil {
|
if err1 == nil {
|
||||||
logger.Debug("Inbound disabled by api:", tag)
|
logger.Debug("Inbound disabled by api:", tag)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Error in disabling inbound by api:", err1)
|
logger.Debug("Error in disabling inbound by api:", err1)
|
||||||
@@ -1071,7 +1083,9 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
|
|||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"total": client.TotalGB,
|
"total": client.TotalGB,
|
||||||
"expiry_time": client.ExpiryTime,
|
"expiry_time": client.ExpiryTime,
|
||||||
"reset": client.Reset})
|
"reset": client.Reset,
|
||||||
|
})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1570,7 +1584,6 @@ func (s *InboundService) ResetAllClientTraffics(id int) error {
|
|||||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1585,7 +1598,6 @@ func (s *InboundService) ResetAllTraffics() error {
|
|||||||
Updates(map[string]interface{}{"up": 0, "down": 0})
|
Updates(map[string]interface{}{"up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1811,7 +1823,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
clients, ok := settings["clients"].([]interface{})
|
clients, ok := settings["clients"].([]interface{})
|
||||||
if ok {
|
if ok {
|
||||||
// Fix Clinet configuration problems
|
// Fix Client configuration problems
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]interface{})
|
||||||
@@ -1897,6 +1909,13 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
newStream, _ := json.MarshalIndent(stream, " ", " ")
|
newStream, _ := json.MarshalIndent(stream, " ", " ")
|
||||||
tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream)
|
tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = tx.Raw(`UPDATE inbounds
|
||||||
|
SET tag = REPLACE(tag, '0.0.0.0:', '')
|
||||||
|
WHERE INSTR(tag, '0.0.0.0:') > 0;`).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrateDB() {
|
func (s *InboundService) MigrateDB() {
|
||||||
|
|||||||
100
web/service/outbound.go
Normal file
100
web/service/outbound.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/xray"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutboundService struct{}
|
||||||
|
|
||||||
|
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||||
|
var err error
|
||||||
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = s.addOutboundTraffic(tx, traffics)
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
||||||
|
if len(traffics) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
if traffic.IsOutbound {
|
||||||
|
|
||||||
|
var outbound model.OutboundTraffics
|
||||||
|
|
||||||
|
err = tx.Model(&model.OutboundTraffics{}).Where("tag = ?", traffic.Tag).
|
||||||
|
FirstOrCreate(&outbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.Tag = traffic.Tag
|
||||||
|
outbound.Up = outbound.Up + traffic.Up
|
||||||
|
outbound.Down = outbound.Down + traffic.Down
|
||||||
|
outbound.Total = outbound.Up + outbound.Down
|
||||||
|
|
||||||
|
err = tx.Save(&outbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var traffics []*model.OutboundTraffics
|
||||||
|
|
||||||
|
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return traffics, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OutboundService) ResetOutboundTraffic(tag string) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
whereText := "tag "
|
||||||
|
if tag == "-alltags-" {
|
||||||
|
whereText += " <> ?"
|
||||||
|
} else {
|
||||||
|
whereText += " = ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
result := db.Model(model.OutboundTraffics{}).
|
||||||
|
Where(whereText, tag).
|
||||||
|
Updates(map[string]interface{}{"up": 0, "down": 0, "total": 0})
|
||||||
|
|
||||||
|
err := result.Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -4,11 +4,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PanelService struct {
|
type PanelService struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PanelService) RestartPanel(delay time.Duration) error {
|
func (s *PanelService) RestartPanel(delay time.Duration) error {
|
||||||
p, err := os.FindProcess(syscall.Getpid())
|
p, err := os.FindProcess(syscall.Getpid())
|
||||||
|
|||||||
@@ -382,7 +382,6 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
|
func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user