mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-19 15:25:49 +00:00
Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
905ffda848 | ||
|
|
b946f7a1d4 | ||
|
|
aaa0f66a37 | ||
|
|
ebbe2c8e5b | ||
|
|
0fdd4dcfb9 | ||
|
|
78fb3844fe | ||
|
|
9452f4fc15 | ||
|
|
5996f92fe5 | ||
|
|
4cabe80462 | ||
|
|
1cf0d52433 | ||
|
|
3cef5142d9 | ||
|
|
ac8af25cef | ||
|
|
aee2107f30 | ||
|
|
7971082beb | ||
|
|
d60290d0a1 | ||
|
|
0b1bc1908b | ||
|
|
a342ef9e90 | ||
|
|
317e4f1fdd | ||
|
|
fd82e701b6 | ||
|
|
046be5dd0f | ||
|
|
fa4c954444 | ||
|
|
03e1434051 | ||
|
|
5725420197 | ||
|
|
fd64ae5c85 | ||
|
|
ff59bb60ce | ||
|
|
7ec646fd0d | ||
|
|
902368fc03 | ||
|
|
57827225b4 | ||
|
|
d406d2925a | ||
|
|
2f2876ec90 | ||
|
|
20d00d31a1 | ||
|
|
48b327ccb5 | ||
|
|
9cc893a396 | ||
|
|
91e9c2d6b2 | ||
|
|
6bfaad48fd | ||
|
|
10779201f5 | ||
|
|
5b20505515 | ||
|
|
ed5db9390d | ||
|
|
5ea3333367 | ||
|
|
9a95530742 | ||
|
|
b5eb368b89 | ||
|
|
1f026d0039 | ||
|
|
056de927da | ||
|
|
4596a981d6 | ||
|
|
5cebf8faef | ||
|
|
2cf2da8ae2 | ||
|
|
da82d14c18 | ||
|
|
8da966a7f7 | ||
|
|
81b96507d0 | ||
|
|
0986791fb3 | ||
|
|
d4317a0a62 | ||
|
|
5fc7f132a1 | ||
|
|
bdb371367e | ||
|
|
407bebad05 | ||
|
|
10d85f9855 | ||
|
|
98c11dd83e | ||
|
|
86f6050697 | ||
|
|
e488dc18b1 | ||
|
|
7ed106a0f0 | ||
|
|
1f0c9acd89 | ||
|
|
99f9788175 | ||
|
|
f64e9dff8a | ||
|
|
1e0dabb434 | ||
|
|
30bb4efcc4 | ||
|
|
5028cf8e08 | ||
|
|
2736779353 | ||
|
|
a45760a9a3 | ||
|
|
d2f6938177 | ||
|
|
cbc4c144f2 | ||
|
|
3ec5ca3e8f | ||
|
|
5c0e55c708 | ||
|
|
d4942a7d83 | ||
|
|
3a913ca81a | ||
|
|
3dd8dbdecf | ||
|
|
02053fea83 | ||
|
|
b69f0cfe82 | ||
|
|
e2912448d6 | ||
|
|
211d31390d | ||
|
|
b10759d462 | ||
|
|
e372575f58 | ||
|
|
59d62afab5 | ||
|
|
5546883799 | ||
|
|
3eae6300e7 | ||
|
|
a8878bc1dc | ||
|
|
b41aef37e5 | ||
|
|
d68decd17b | ||
|
|
5f9ae30b71 | ||
|
|
1f4ff4b985 | ||
|
|
1daa8b8d7a | ||
|
|
ed8ef18460 | ||
|
|
f4cda8a4c0 | ||
|
|
144d1e79c8 | ||
|
|
7410b80e7a | ||
|
|
54197993db | ||
|
|
878e5507e6 | ||
|
|
94387d48e3 | ||
|
|
cfc11151d2 | ||
|
|
0dbb67d0bb | ||
|
|
a4b447e2d7 | ||
|
|
ca155bd871 | ||
|
|
fb8f27ad49 | ||
|
|
a88044500e | ||
|
|
3d0663d793 | ||
|
|
1735faa193 | ||
|
|
d1980581d9 | ||
|
|
052ceb5a2d | ||
|
|
bd075ed81d | ||
|
|
04b52ae637 | ||
|
|
739b4a1782 | ||
|
|
2ca3a05cca | ||
|
|
5e07e32388 | ||
|
|
871d46be72 | ||
|
|
0fb9c2e858 | ||
|
|
cef94ed4bc | ||
|
|
a0a19a4d2e | ||
|
|
886a300c64 | ||
|
|
e78e7c99cd | ||
|
|
a93461e2c2 | ||
|
|
5cfe617841 | ||
|
|
cc5542b138 | ||
|
|
7c74c534f0 | ||
|
|
46dcff2fe7 | ||
|
|
127430f227 | ||
|
|
c0802c8c71 | ||
|
|
b845635bb5 | ||
|
|
d4a23f8a23 | ||
|
|
f424d1bbc0 | ||
|
|
935ff96eeb | ||
|
|
95318f51c5 | ||
|
|
0d77b52f39 | ||
|
|
6b12c314be | ||
|
|
9c35468d3d | ||
|
|
0411281d8e | ||
|
|
6cb0012622 | ||
|
|
25e4bcedb1 | ||
|
|
aea474c7ef | ||
|
|
6afddc7bee | ||
|
|
ce54535df5 | ||
|
|
31155ecff9 | ||
|
|
ffb05f596b | ||
|
|
8917c7291d | ||
|
|
a88d8eadee | ||
|
|
80bb9f7953 | ||
|
|
16113ce7aa | ||
|
|
de4affb913 | ||
|
|
3230a81a7c | ||
|
|
df86f7bc29 | ||
|
|
ac5981352e | ||
|
|
53719ecf6a | ||
|
|
9f3502d912 |
14
.github/workflows/docker.yml
vendored
14
.github/workflows/docker.yml
vendored
@@ -10,13 +10,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
alireza7/x-ui
|
alireza7/x-ui
|
||||||
@@ -27,26 +27,26 @@ jobs:
|
|||||||
type=pep440,pattern={{version}}
|
type=pep440,pattern={{version}}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -10,9 +10,9 @@ jobs:
|
|||||||
name: build x-ui amd64 version
|
name: build x-ui amd64 version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.21'
|
||||||
- name: build linux amd64 version
|
- name: build linux amd64 version
|
||||||
@@ -26,12 +26,13 @@ jobs:
|
|||||||
mv xui-release x-ui
|
mv xui-release x-ui
|
||||||
mkdir bin
|
mkdir bin
|
||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-64.zip
|
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
rm -f Xray-linux-64.zip geoip.dat geosite.dat LICENSE README.md
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat
|
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||||
|
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||||
mv xray xray-linux-amd64
|
mv xray xray-linux-amd64
|
||||||
cd ..
|
cd ..
|
||||||
cd ..
|
cd ..
|
||||||
@@ -49,9 +50,9 @@ jobs:
|
|||||||
name: build x-ui arm64 version
|
name: build x-ui arm64 version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.21'
|
||||||
- name: build linux arm64 version
|
- name: build linux arm64 version
|
||||||
@@ -67,12 +68,13 @@ jobs:
|
|||||||
mv xui-release x-ui
|
mv xui-release x-ui
|
||||||
mkdir bin
|
mkdir bin
|
||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/xtls/xray-core/releases/download/v1.8.4/Xray-linux-arm64-v8a.zip
|
wget https://github.com/xtls/xray-core/releases/download/v1.8.6/Xray-linux-arm64-v8a.zip
|
||||||
unzip Xray-linux-arm64-v8a.zip
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat LICENSE README.md
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.dat
|
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||||
|
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||||
mv xray xray-linux-arm64
|
mv xray xray-linux-arm64
|
||||||
cd ..
|
cd ..
|
||||||
cd ..
|
cd ..
|
||||||
@@ -90,9 +92,9 @@ jobs:
|
|||||||
name: build x-ui s390x version
|
name: build x-ui s390x version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.21'
|
||||||
- name: build linux s390x version
|
- name: build linux s390x version
|
||||||
@@ -108,12 +110,13 @@ jobs:
|
|||||||
mv xui-release x-ui
|
mv xui-release x-ui
|
||||||
mkdir bin
|
mkdir bin
|
||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/xtls/xray-core/releases/download/v1.8.4/Xray-linux-s390x.zip
|
wget https://github.com/xtls/xray-core/releases/download/v1.8.6/Xray-linux-s390x.zip
|
||||||
unzip Xray-linux-s390x.zip
|
unzip Xray-linux-s390x.zip
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat LICENSE README.md
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.dat
|
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||||
|
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||||
mv xray xray-linux-s390x
|
mv xray xray-linux-s390x
|
||||||
cd ..
|
cd ..
|
||||||
cd ..
|
cd ..
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ dist/
|
|||||||
release/
|
release/
|
||||||
/release.sh
|
/release.sh
|
||||||
/x-ui
|
/x-ui
|
||||||
|
.DS_Store
|
||||||
@@ -11,11 +11,12 @@ else
|
|||||||
fi
|
fi
|
||||||
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.4/Xray-linux-${ARCH}.zip"
|
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||||
wget "https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.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
|
||||||
cd ../../
|
cd ../../
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ WORKDIR /app
|
|||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
RUN apk --no-cache --update add build-base gcc wget unzip
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN env CGO_ENABLED=1 go build -o build/x-ui main.go
|
ENV CGO_ENABLED=1
|
||||||
|
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||||
|
RUN go build -o build/x-ui main.go
|
||||||
RUN ./DockerInitFiles.sh "$TARGETARCH"
|
RUN ./DockerInitFiles.sh "$TARGETARCH"
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
|||||||
198
README.md
198
README.md
@@ -1,4 +1,5 @@
|
|||||||
# x-ui
|
# X-UI
|
||||||
|
**Advanced GUI panel based on Xray Core supports multiple protocols and languages**
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -8,35 +9,41 @@
|
|||||||
|
|
||||||
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
||||||
|
|
||||||
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)**
|
|
||||||
|
|
||||||
| Features | Enable? |
|
|
||||||
| ------------------------------------ | :----------------: |
|
|
||||||
| Multi-lang | :heavy_check_mark: |
|
|
||||||
| Dark/Light Theme | :heavy_check_mark: |
|
|
||||||
| Search in deep | :heavy_check_mark: |
|
|
||||||
| Inbound Multi User | :heavy_check_mark: |
|
|
||||||
| Multi User Traffic & Expiration time | :heavy_check_mark: |
|
|
||||||
| REST API | :heavy_check_mark: |
|
|
||||||
| Telegram BOT (admin + clients) | :heavy_check_mark: |
|
|
||||||
| Backup database using Telegram BOT | :heavy_check_mark: |
|
|
||||||
| Subscription link + userInfo | :heavy_check_mark: |
|
|
||||||
| Calculate expire date on first usage | :heavy_check_mark: |
|
|
||||||
|
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
**Buy Me a Coffee :**
|
**Buy Me a Coffee :**
|
||||||
|
|
||||||
- Tron USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
|
- USDT Tron (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
|
||||||
- Tezos (XTZ): tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts
|
- Tezos (XTZ):
|
||||||
|
`tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts`
|
||||||
|
|
||||||
# Install & Upgrade to latest version
|
|
||||||
|
## Quick Look
|
||||||
|
| Features | Enable? |
|
||||||
|
| -------------------------------------- | :----------------: |
|
||||||
|
| Multi-Protocol | :heavy_check_mark: |
|
||||||
|
| Multi-Language | :heavy_check_mark: |
|
||||||
|
| Multi-User Inbounds | :heavy_check_mark: |
|
||||||
|
| Advanced Traffic Routing | :heavy_check_mark: |
|
||||||
|
| REST API | :heavy_check_mark: |
|
||||||
|
|Show Online Users | :heavy_check_mark: |
|
||||||
|
| Manage Users Traffic Data & Expiry Date| :heavy_check_mark: |
|
||||||
|
| Apply Expiry Date based on First Usage | :heavy_check_mark: |
|
||||||
|
| Telegram Bot (admin + clients) | :heavy_check_mark: |
|
||||||
|
| Database Backup using Telegram Bot | :heavy_check_mark: |
|
||||||
|
| Subscription Link + UserInfo | :heavy_check_mark: |
|
||||||
|
| Search in Deep | :heavy_check_mark: |
|
||||||
|
| Dark/Light Theme | :heavy_check_mark: |
|
||||||
|
|
||||||
|
|
||||||
|
## Install & Upgrade to Latest Version
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install custom version
|
## Install Custom Version
|
||||||
|
|
||||||
To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
|
To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
|
||||||
|
|
||||||
@@ -44,10 +51,10 @@ To install your desired version you can add the version to the end of install co
|
|||||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
|
||||||
```
|
```
|
||||||
|
|
||||||
## Manual install & upgrade
|
## Manual Install & Upgrade
|
||||||
|
|
||||||
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases, generally choose Architecture `amd64`
|
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases, generally choose Architecture `amd64`
|
||||||
2. Then upload the compressed package to the server's `/root/` directory and `root` rootlog in to the server with user
|
2. Then upload the compressed package to the server's `/root/` directory and login to the server with user `root`
|
||||||
|
|
||||||
> If your server cpu architecture is not `amd64` replace another architecture
|
> If your server cpu architecture is not `amd64` replace another architecture
|
||||||
|
|
||||||
@@ -66,15 +73,15 @@ systemctl enable x-ui
|
|||||||
systemctl restart x-ui
|
systemctl restart x-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install using docker
|
## Install Using Docker
|
||||||
|
|
||||||
1. install docker
|
1. Install Docker
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl -fsSL https://get.docker.com | sh
|
curl -fsSL https://get.docker.com | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
2. install x-ui
|
2. Install X-UI
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mkdir x-ui && cd x-ui
|
mkdir x-ui && cd x-ui
|
||||||
@@ -93,59 +100,90 @@ docker run -itd \
|
|||||||
docker build -t x-ui .
|
docker build -t x-ui .
|
||||||
```
|
```
|
||||||
|
|
||||||
# Features
|
## Languages
|
||||||
|
|
||||||
- System Status Monitoring
|
- English
|
||||||
- Search within all inbounds and clients
|
- Chinese
|
||||||
|
- Farsi
|
||||||
|
- Russian
|
||||||
|
- Vietnamese
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Supported protocols: VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, SOCKS, HTTP
|
||||||
|
- Support XTLS native encryptions (Vision, REALITY)
|
||||||
|
- Support advanced JSON editor GUI for Xray-Core configuration
|
||||||
|
- Support advanced GUI for routing traffic (Reverse and Transparent proxy, Multi-Domain, Multi-Certificate, Multi-Port per inbound)
|
||||||
|
- Support Multi-User per inbound
|
||||||
|
- Support applying traffic data limits and expiry dates per user/inbound
|
||||||
|
- Support system status monitoring
|
||||||
|
- Support deep database search
|
||||||
|
- Show traffic statistics
|
||||||
|
- Show online users
|
||||||
|
- Show users with expired date or exceeded traffic limits
|
||||||
|
- Support subscription (multi) link
|
||||||
|
- Support import/export database
|
||||||
|
- Support One-Click SSL certificate application and automatic renewal
|
||||||
|
- Support HTTPS for panel (self-provided domain name + SSL certificate)
|
||||||
- Support Dark/Light theme UI
|
- Support Dark/Light theme UI
|
||||||
- Support multi-user multi-protocol, web page visualization operation
|
|
||||||
- Support multi-domain configuration and multi-certificate inbounds
|
|
||||||
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
|
||||||
- Support for configuring more transport configurations
|
|
||||||
- Traffic statistics, limit traffic, limit expiration time
|
|
||||||
- Customizable xray configuration templates
|
|
||||||
- Support subscription ( multi ) link
|
|
||||||
- Detect users which are expiring or exceed traffic limit soon
|
|
||||||
- Support https access panel (self-provided domain name + ssl certificate)
|
|
||||||
- Support one-click SSL certificate application and automatic renewal
|
|
||||||
- For more advanced configuration items, please refer to the panel
|
|
||||||
- Support export/import database from panel
|
|
||||||
|
|
||||||
## suggestion system
|
## Recommended OS
|
||||||
|
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- Ubuntu 20+
|
- Ubuntu 20+
|
||||||
- Debian 10+
|
- Debian 10+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
|
|
||||||
## API routes
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## API Routes
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
||||||
- `/xui/API/inbounds` base for following actions:
|
- `/xui/API/inbounds` base for following actions:
|
||||||
|
|
||||||
| Method | Path | Action |
|
| Method | Path | Action |
|
||||||
| :----: | ------------------------------- | ----------------------------------------- |
|
| :----: | --------------------------------- | ----------------------------------------- |
|
||||||
| `GET` | `"/"` | Get all inbounds |
|
| `GET` | `"/"` | Get all inbounds |
|
||||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||||
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
||||||
| `POST` | `"/add"` | Add inbound |
|
| `POST` | `"/add"` | Add inbound |
|
||||||
| `POST` | `"/del/:id"` | Delete Inbound |
|
| `POST` | `"/del/:id"` | Delete inbound |
|
||||||
| `POST` | `"/update/:id"` | Update Inbound |
|
| `POST` | `"/update/:id"` | Update inbound |
|
||||||
| `POST` | `"/addClient/"` | Add Client to inbound |
|
| `POST` | `"/addClient/"` | Add client to inbound |
|
||||||
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* |
|
| `POST` | `"/:id/delClient/:clientId"` | Delete client by clientId\* |
|
||||||
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* |
|
| `POST` | `"/updateClient/:clientId"` | Update client by clientId\* |
|
||||||
| `POST` | `"/getClientTraffics/:email"` | Get Client's Traffic |
|
| `GET` | `"/getClientTraffics/:email"` | Get client's traffic |
|
||||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset client's traffic |
|
||||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
|
||||||
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
|
| `POST` | `"/onlines"` | Get online users ( list of emails ) |
|
||||||
|
|
||||||
\*- The field `clientId` should be filled by:
|
\*- The field `clientId` should be filled by:
|
||||||
|
|
||||||
- `client.id` for VMESS and VLESS
|
- `client.id` for VMess and VLESS
|
||||||
- `client.password` for TROJAN
|
- `client.password` for Trojan
|
||||||
- `client.email` for Shadowsocks
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
# Environment Variables
|
</details>
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
| Variable | Type | Default |
|
| Variable | Type | Default |
|
||||||
| -------------- | :--------------------------------------------: | :------------ |
|
| -------------- | :--------------------------------------------: | :------------ |
|
||||||
@@ -154,12 +192,9 @@ docker build -t x-ui .
|
|||||||
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||||
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||||
|
|
||||||
# Screenshot from Inbouds page
|
</details>
|
||||||
|
|
||||||

|
## SSL Certificate
|
||||||

|
|
||||||
|
|
||||||
## SSL certificate application
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Click for details</summary>
|
<summary>Click for details</summary>
|
||||||
@@ -176,11 +211,13 @@ certbot certonly --standalone --register-unsafely-without-email --non-interactiv
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Tg robot use
|
## Telegram Bot
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Click for details</summary>
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
||||||
Set the robot-related parameters in the panel background, including:
|
Set the robot-related parameters in the panel background, including:
|
||||||
|
|
||||||
@@ -200,13 +237,13 @@ Reference syntax:
|
|||||||
- @daily // Daily notification (00:00 in the morning)
|
- @daily // Daily notification (00:00 in the morning)
|
||||||
- @every 8h // notify every 8 hours
|
- @every 8h // notify every 8 hours
|
||||||
|
|
||||||
### Telegram Bot Features
|
### Features
|
||||||
|
|
||||||
- Report periodic
|
- Report periodic
|
||||||
- Login notification
|
- Login notification
|
||||||
- CPU threshold notification
|
- CPU threshold notification
|
||||||
- Threshold for Expiration time and Traffic to report in advance
|
- Threshold for Expiration time and Traffic to report in advance
|
||||||
- Support client report menu if client's telegram username added to the user's configurations
|
- Support client report menu if client's telegram ID or telegram UserName added to the user's configurations
|
||||||
- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
||||||
- Menu based bot
|
- Menu based bot
|
||||||
- Search client by email ( only admin )
|
- Search client by email ( only admin )
|
||||||
@@ -217,18 +254,23 @@ Reference syntax:
|
|||||||
- Multi language bot
|
- Multi language bot
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
# T-Shoots:
|
## Troubleshoots
|
||||||
|
|
||||||
**If you upgrade from an old version or other forks, for enable traffic for users you should do :**
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
find this in config :
|
### Enable Traffic Usage
|
||||||
|
|
||||||
|
Please be aware if you upgrade from an old X-UI version or other forks, by default data traffic usage for users may not work! it's recommended to follow below steps for enabeling:
|
||||||
|
|
||||||
|
1. Find this section in config file
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"policy": {
|
"policy": {
|
||||||
"system": {
|
"system": {
|
||||||
```
|
```
|
||||||
|
|
||||||
**and add this just after ` "policy": {` :**
|
2. Add below section just after ` "policy": {` :
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"levels": {
|
"levels": {
|
||||||
@@ -239,7 +281,7 @@ find this in config :
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
**the final output is like :**
|
- The final output is like:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"policy": {
|
"policy": {
|
||||||
@@ -258,20 +300,20 @@ find this in config :
|
|||||||
"routing": {
|
"routing": {
|
||||||
```
|
```
|
||||||
|
|
||||||
restart panel
|
3. Save and restart panel
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
# a special thanks to
|
## a Special Thanks to
|
||||||
|
|
||||||
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
||||||
- [MHSanaei](https://github.com/MHSanaei)
|
- [MHSanaei](https://github.com/MHSanaei)
|
||||||
|
|
||||||
# Acknowledgment
|
## Acknowledgment
|
||||||
|
|
||||||
- [Iran Hosted Domains](https://github.com/bootmortis/iran-hosted-domains) (License: **MIT**): _A comprehensive list of Iranian domains and services that are hosted within the country._
|
- [Loyalsoldier](https://github.com/Loyalsoldier/v2ray-rules-dat) (License: **GPL-3.0**): _The enhanced version of V2Ray routing rule._
|
||||||
- [PersianBlocker](https://github.com/MasterKia/PersianBlocker) (License: **AGPLv3**): _An optimal and extensive list to block ads and trackers on Persian websites._
|
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over Time
|
||||||
|
|
||||||
[](https://starchart.cc/alireza0/x-ui)
|
[](https://starchart.cc/alireza0/x-ui)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.5.5
|
1.6.2
|
||||||
@@ -110,3 +110,12 @@ func IsSQLiteDB(file io.Reader) (bool, error) {
|
|||||||
}
|
}
|
||||||
return bytes.Equal(buf, signature), nil
|
return bytes.Equal(buf, signature), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Checkpoint() error {
|
||||||
|
// Update WAL
|
||||||
|
err := db.Exec("PRAGMA wal_checkpoint;").Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,4 +76,5 @@ type Client struct {
|
|||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
TgID string `json:"tgId" form:"tgId"`
|
TgID string `json:"tgId" form:"tgId"`
|
||||||
SubID string `json:"subId" form:"subId"`
|
SubID string `json:"subId" form:"subId"`
|
||||||
|
Reset int `json:"reset" form:"reset"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
version: "3.9"
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
xui:
|
xui:
|
||||||
|
|||||||
75
go.mod
75
go.mod
@@ -1,92 +1,95 @@
|
|||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.21
|
go 1.21.4
|
||||||
|
|
||||||
toolchain go1.21.0
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Workiva/go-datastructures v1.1.0
|
github.com/Workiva/go-datastructures v1.1.1
|
||||||
github.com/gin-contrib/sessions v0.0.4
|
github.com/gin-contrib/sessions v0.0.4
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
github.com/nicksnyder/go-i18n/v2 v2.3.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.0
|
github.com/pelletier/go-toml/v2 v2.1.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.7
|
github.com/shirou/gopsutil/v3 v3.23.11
|
||||||
github.com/xtls/xray-core v1.8.4
|
github.com/xtls/xray-core v1.8.6
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.12.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/grpc v1.57.0
|
google.golang.org/grpc v1.59.0
|
||||||
gorm.io/driver/sqlite v1.5.3
|
gorm.io/driver/sqlite v1.5.4
|
||||||
gorm.io/gorm v1.25.4
|
gorm.io/gorm v1.25.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
|
github.com/cloudflare/circl v1.3.6 // 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/francoispqt/gojay v1.2.13 // indirect
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||||
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // 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.14.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.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/mock v1.6.0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
github.com/gorilla/sessions v1.2.1 // indirect
|
github.com/gorilla/sessions v1.2.1 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.1 // 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.16.7 // indirect
|
github.com/klauspost/compress v1.17.2 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.13.1 // 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.3.3 // indirect
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.38.1 // indirect
|
github.com/quic-go/quic-go v0.40.0 // indirect
|
||||||
github.com/refraction-networking/utls v1.4.3 // indirect
|
github.com/refraction-networking/utls v1.5.4 // 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/sagernet/sing v0.2.9 // indirect
|
github.com/sagernet/sing v0.2.17 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.4 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.5 // indirect
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // 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.11 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 // indirect
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||||
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
|
go.uber.org/mock v0.3.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.12.0 // indirect
|
golang.org/x/crypto v0.15.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||||
golang.org/x/mod v0.12.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/net v0.14.0 // indirect
|
golang.org/x/net v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.11.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.4.0 // indirect
|
||||||
golang.org/x/tools v0.12.0 // indirect
|
golang.org/x/tools v0.15.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 // indirect
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
|
||||||
lukechampine.com/blake3 v1.2.1 // indirect
|
lukechampine.com/blake3 v1.2.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
220
go.sum
220
go.sum
@@ -8,13 +8,12 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
|
|||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
|
||||||
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
|
github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
github.com/Workiva/go-datastructures v1.1.0/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
|
||||||
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/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
@@ -30,7 +29,10 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F
|
|||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
|
||||||
|
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -49,36 +51,43 @@ github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67d
|
|||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||||
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
||||||
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 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/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
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/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
@@ -101,15 +110,16 @@ github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76
|
|||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/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-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/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=
|
||||||
@@ -121,8 +131,8 @@ github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE
|
|||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
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=
|
||||||
@@ -137,18 +147,22 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||||||
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/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
@@ -158,6 +172,7 @@ github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQ
|
|||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 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.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
@@ -165,8 +180,8 @@ github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
|
|||||||
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/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||||
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=
|
||||||
@@ -176,22 +191,24 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/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.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||||
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
|
github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
|
||||||
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
|
github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
github.com/onsi/gomega v1.29.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=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@@ -204,28 +221,29 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:
|
|||||||
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/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
|
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
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.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
|
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
||||||
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
|
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||||
github.com/refraction-networking/utls v1.4.3 h1:BdWS3BSzCwWCFfMIXP3mjLAyQkdmog7diaD/OqFbAzM=
|
github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
|
||||||
github.com/refraction-networking/utls v1.4.3/go.mod h1:4u9V/awOSBrRw6+federGmVJQfPtemEqLBXkML1b0bo=
|
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
|
||||||
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=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/sagernet/sing v0.2.9 h1:3wsTz+JG5Wzy65eZnh6AuCrD2QqcRF6Iq6f7ttmJsAo=
|
github.com/sagernet/sing v0.2.17 h1:vMPKb3MV0Aa5ws4dCJkRI8XEjrsUcDn810czd0FwmzI=
|
||||||
github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
github.com/sagernet/sing v0.2.17/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
|
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
|
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
|
||||||
github.com/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.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||||
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,33 +288,40 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
|||||||
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/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/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/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/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/xtls/reality v0.0.0-20230828171259-e426190d57f6 h1:T+YCYGfFdzyaKTDCdZn/hEiKvsw6yUfd+e4hze0rCUw=
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
|
||||||
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y=
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mod h1:cAAsePK2e15YDAMJNyOpGYEWNe4sIghTY7gpz4cX/Ik=
|
||||||
github.com/xtls/xray-core v1.8.4 h1:YEoY3iLx/5zoNbt5HORG5LtPyzwICInFfoS+oPkYDIw=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/xtls/xray-core v1.8.4/go.mod h1:GGD9elFSHa4IqOArW8gzMsEksPIqK/jdNLo8RcSMfnI=
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
|
||||||
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||||
|
github.com/xtls/xray-core v1.8.6 h1:tr3nk/fZnFfCsmgZv7B3RC72N5qUC88oMGVLlybDey8=
|
||||||
|
github.com/xtls/xray-core v1.8.6/go.mod h1:hj2EB8rtcLdlTC8zxiWm5xL+C0k2Aie9Pk0mXtDEP6U=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.3/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=
|
||||||
|
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||||
|
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
@@ -309,20 +334,18 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||||
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-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||||
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-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=
|
||||||
@@ -335,10 +358,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-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=
|
||||||
@@ -350,10 +371,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -362,34 +381,32 @@ golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.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 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
|
||||||
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 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/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=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -397,14 +414,16 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
|
||||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
@@ -417,39 +436,42 @@ 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-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
||||||
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.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 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.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||||
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||||
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.5/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-20230822212503-5bf4e5f98744 h1:tE44CyJgxEGzoPtHs9GI7ddKdgEGCREQBP54AmaVM+I=
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
|
||||||
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744/go.mod h1:lYEMhXbxgudVhALYsMQrBaUAjM3NMinh8mKL1CJv7rc=
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -26,17 +26,15 @@ func InitLogger(level logging.Level) {
|
|||||||
var format logging.Formatter
|
var format logging.Formatter
|
||||||
ppid := os.Getppid()
|
ppid := os.Getppid()
|
||||||
|
|
||||||
if ppid == 1 {
|
backend, err = logging.NewSyslogBackend("")
|
||||||
backend, err = logging.NewSyslogBackend("")
|
if err != nil {
|
||||||
format = logging.MustStringFormatter(
|
println(err)
|
||||||
`%{level} - %{message}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if err != nil || ppid != 1 {
|
|
||||||
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
format = logging.MustStringFormatter(
|
}
|
||||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
if ppid > 0 && err != nil {
|
||||||
)
|
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
|
||||||
|
} else {
|
||||||
|
format = logging.MustStringFormatter(`%{level} - %{message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 167 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 172 KiB |
BIN
media/outbounds.png
Normal file
BIN
media/outbounds.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
BIN
media/rules.png
Normal file
BIN
media/rules.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
@@ -19,8 +19,9 @@ import (
|
|||||||
type SubService struct {
|
type SubService struct {
|
||||||
address string
|
address string
|
||||||
showInfo bool
|
showInfo bool
|
||||||
|
remarkModel string
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
settingServics service.SettingService
|
settingService service.SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
||||||
@@ -34,6 +35,10 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
s.remarkModel, err = s.settingService.GetRemarkModel()
|
||||||
|
if err != nil {
|
||||||
|
s.remarkModel = "-ieo"
|
||||||
|
}
|
||||||
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 {
|
||||||
@@ -53,6 +58,7 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
|||||||
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
|
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
|
||||||
stream["security"] = masterStream["security"]
|
stream["security"] = masterStream["security"]
|
||||||
stream["tlsSettings"] = masterStream["tlsSettings"]
|
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||||
|
stream["externalProxy"] = masterStream["externalProxy"]
|
||||||
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
||||||
inbound.StreamSettings = string(modifiedStream)
|
inbound.StreamSettings = string(modifiedStream)
|
||||||
}
|
}
|
||||||
@@ -87,7 +93,7 @@ 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))
|
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
||||||
updateInterval, _ := s.settingServics.GetSubUpdates()
|
updateInterval, _ := s.settingService.GetSubUpdates()
|
||||||
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||||
headers = append(headers, subId)
|
headers = append(headers, subId)
|
||||||
return result, headers, nil
|
return result, headers, nil
|
||||||
@@ -96,7 +102,14 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
|||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Preload("ClientStats").Where(`id in (
|
||||||
|
SELECT DISTINCT inbounds.id
|
||||||
|
FROM inbounds,
|
||||||
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
|
WHERE
|
||||||
|
protocol in ('vmess','vless','trojan','shadowsocks')
|
||||||
|
AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ?
|
||||||
|
)`, subId, true).Find(&inbounds).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -196,7 +209,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
var domains []interface{}
|
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -208,24 +220,18 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
obj["alpn"] = strings.Join(alpn, ",")
|
obj["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
|
obj["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
|
||||||
obj["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
obj["fp"], _ = fpValue.(string)
|
obj["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
obj["allowInsecure"], _ = insecure.(bool)
|
obj["allowInsecure"], _ = insecure.(bool)
|
||||||
}
|
}
|
||||||
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
|
||||||
domains, _ = domainSettings.([]interface{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
|
||||||
if serverName != "" {
|
|
||||||
obj["add"] = serverName
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,16 +245,30 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
obj["id"] = clients[clientIndex].ID
|
obj["id"] = clients[clientIndex].ID
|
||||||
|
|
||||||
if len(domains) > 0 {
|
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||||
|
|
||||||
|
if len(externalProxies) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
for index, d := range domains {
|
for index, externalProxy := range externalProxies {
|
||||||
domain := d.(map[string]interface{})
|
ep, _ := externalProxy.(map[string]interface{})
|
||||||
obj["ps"] = s.genRemark(inbound, email, domain["remark"].(string))
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
obj["add"] = domain["domain"].(string)
|
newObj := map[string]interface{}{}
|
||||||
|
for key, value := range obj {
|
||||||
|
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
|
||||||
|
newObj[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string))
|
||||||
|
newObj["add"] = ep["dest"].(string)
|
||||||
|
newObj["port"] = int(ep["port"].(float64))
|
||||||
|
|
||||||
|
if newSecurity != "same" {
|
||||||
|
newObj["tls"] = newSecurity
|
||||||
|
}
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
links += "\n"
|
links += "\n"
|
||||||
}
|
}
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
jsonStr, _ := json.MarshalIndent(newObj, "", " ")
|
||||||
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
}
|
}
|
||||||
return links
|
return links
|
||||||
@@ -323,7 +343,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
var domains []interface{}
|
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -335,11 +354,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
if len(alpn) > 0 {
|
if len(alpn) > 0 {
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
params["fp"], _ = fpValue.(string)
|
params["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
@@ -348,19 +368,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
|
||||||
domains, _ = domainSettings.([]interface{})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
params["flow"] = clients[clientIndex].Flow
|
params["flow"] = clients[clientIndex].Flow
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
|
||||||
if serverName != "" {
|
|
||||||
address = serverName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if security == "reality" {
|
if security == "reality" {
|
||||||
@@ -389,11 +401,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["spx"] = spx
|
params["spx"] = spx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
|
||||||
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
|
||||||
address = sname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -401,6 +408,44 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||||
|
|
||||||
|
if len(externalProxies) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, externalProxy := range externalProxies {
|
||||||
|
ep, _ := externalProxy.(map[string]interface{})
|
||||||
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
|
dest, _ := ep["dest"].(string)
|
||||||
|
port := int(ep["port"].(float64))
|
||||||
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
|
||||||
|
|
||||||
|
if newSecurity != "same" {
|
||||||
|
params["security"] = newSecurity
|
||||||
|
} else {
|
||||||
|
params["security"] = security
|
||||||
|
}
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
||||||
|
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
@@ -412,20 +457,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
if len(domains) > 0 {
|
|
||||||
links := ""
|
|
||||||
for index, d := range domains {
|
|
||||||
domain := d.(map[string]interface{})
|
|
||||||
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
|
||||||
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
|
||||||
if index > 0 {
|
|
||||||
links += "\n"
|
|
||||||
}
|
|
||||||
links += url.String()
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
@@ -493,7 +524,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
var domains []interface{}
|
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -505,11 +535,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
if len(alpn) > 0 {
|
if len(alpn) > 0 {
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
params["fp"], _ = fpValue.(string)
|
params["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
@@ -518,14 +549,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
|
||||||
domains, _ = domainSettings.([]interface{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
|
||||||
if serverName != "" {
|
|
||||||
address = serverName
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,12 +578,45 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["spx"] = spx
|
params["spx"] = spx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
}
|
||||||
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
}
|
||||||
address = sname
|
|
||||||
|
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||||
|
|
||||||
|
if len(externalProxies) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, externalProxy := range externalProxies {
|
||||||
|
ep, _ := externalProxy.(map[string]interface{})
|
||||||
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
|
dest, _ := ep["dest"].(string)
|
||||||
|
port := int(ep["port"].(float64))
|
||||||
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
|
||||||
|
|
||||||
|
if newSecurity != "same" {
|
||||||
|
params["security"] = newSecurity
|
||||||
|
} else {
|
||||||
|
params["security"] = security
|
||||||
|
}
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
||||||
|
q.Add(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
||||||
|
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
}
|
}
|
||||||
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
||||||
@@ -575,20 +631,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
if len(domains) > 0 {
|
|
||||||
links := ""
|
|
||||||
for index, d := range domains {
|
|
||||||
domain := d.(map[string]interface{})
|
|
||||||
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
|
||||||
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
|
||||||
if index > 0 {
|
|
||||||
links += "\n"
|
|
||||||
}
|
|
||||||
links += url.String()
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
@@ -658,10 +700,78 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
security, _ := stream["security"].(string)
|
||||||
|
if security == "tls" {
|
||||||
|
params["security"] = "tls"
|
||||||
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
|
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||||
|
var alpn []string
|
||||||
|
for _, a := range alpns {
|
||||||
|
alpn = append(alpn, a.(string))
|
||||||
|
}
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
|
}
|
||||||
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
|
if tlsSetting != nil {
|
||||||
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
|
params["fp"], _ = fpValue.(string)
|
||||||
|
}
|
||||||
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
|
if insecure.(bool) {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
||||||
if method[0] == '2' {
|
if method[0] == '2' {
|
||||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||||
|
|
||||||
|
if len(externalProxies) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, externalProxy := range externalProxies {
|
||||||
|
ep, _ := externalProxy.(map[string]interface{})
|
||||||
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
|
dest, _ := ep["dest"].(string)
|
||||||
|
port := int(ep["port"].(float64))
|
||||||
|
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port)
|
||||||
|
|
||||||
|
if newSecurity != "same" {
|
||||||
|
params["security"] = newSecurity
|
||||||
|
} else {
|
||||||
|
params["security"] = security
|
||||||
|
}
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
||||||
|
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
@@ -672,22 +782,36 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
|
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
||||||
var remark []string
|
separationChar := string(s.remarkModel[0])
|
||||||
|
orderChars := s.remarkModel[1:]
|
||||||
|
orders := map[byte]string{
|
||||||
|
'i': "",
|
||||||
|
'e': "",
|
||||||
|
'o': "",
|
||||||
|
}
|
||||||
if len(email) > 0 {
|
if len(email) > 0 {
|
||||||
if len(inbound.Remark) > 0 {
|
orders['e'] = email
|
||||||
remark = append(remark, inbound.Remark)
|
}
|
||||||
|
if len(inbound.Remark) > 0 {
|
||||||
|
orders['i'] = inbound.Remark
|
||||||
|
}
|
||||||
|
if len(extra) > 0 {
|
||||||
|
orders['o'] = extra
|
||||||
|
}
|
||||||
|
|
||||||
|
var remark []string
|
||||||
|
for i := 0; i < len(orderChars); i++ {
|
||||||
|
char := orderChars[i]
|
||||||
|
order, exists := orders[char]
|
||||||
|
if exists && order != "" {
|
||||||
|
remark = append(remark, order)
|
||||||
}
|
}
|
||||||
remark = append(remark, email)
|
|
||||||
if len(extra) > 0 {
|
|
||||||
remark = append(remark, extra)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return inbound.Remark
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.showInfo {
|
if s.showInfo {
|
||||||
@@ -704,7 +828,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
|||||||
// Get remained days
|
// Get remained days
|
||||||
if statsExist {
|
if statsExist {
|
||||||
if !stats.Enable {
|
if !stats.Enable {
|
||||||
return fmt.Sprintf("⛔️N/A-%s", strings.Join(remark, "-"))
|
return fmt.Sprintf("⛔️N/A%s%s", separationChar, strings.Join(remark, separationChar))
|
||||||
}
|
}
|
||||||
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
|
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
|
||||||
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
|
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
|
||||||
@@ -718,7 +842,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(remark, "-")
|
return strings.Join(remark, separationChar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
|
|||||||
BIN
web/assets/Vazirmatn-UI-NL-Regular.woff2
Normal file
BIN
web/assets/Vazirmatn-UI-NL-Regular.woff2
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
@import "../lib/style/index.less";
|
|
||||||
@import "../lib/style/components.less";
|
|
||||||
8
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
8
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
File diff suppressed because one or more lines are too long
2
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
2
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
File diff suppressed because one or more lines are too long
3
web/assets/ant-design-vue@1.7.8/antd-with-locales.min.js
vendored
Normal file
3
web/assets/ant-design-vue@1.7.8/antd-with-locales.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
web/assets/ant-design-vue@1.7.8/antd.less
Normal file
6
web/assets/ant-design-vue@1.7.8/antd.less
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@import "../lib/style/index.less";
|
||||||
|
@import "../lib/style/components.less";
|
||||||
|
|
||||||
|
@blue-6: #0E49B5;
|
||||||
|
@border-radius-base: 1rem;
|
||||||
|
@progress-remaining-color: #EDEDED;
|
||||||
8
web/assets/ant-design-vue@1.7.8/antd.min.css
vendored
Normal file
8
web/assets/ant-design-vue@1.7.8/antd.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
3
web/assets/ant-design-vue@1.7.8/antd.min.js
vendored
Normal file
3
web/assets/ant-design-vue@1.7.8/antd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web/assets/ant-design-vue@1.7.8/antd.min.js.map
Normal file
1
web/assets/ant-design-vue@1.7.8/antd.min.js.map
Normal file
File diff suppressed because one or more lines are too long
344
web/assets/codemirror/codemirror.css
Normal file
344
web/assets/codemirror/codemirror.css
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
/* BASICS */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
/* Set height, width, borders, and global font properties here */
|
||||||
|
font-family: monospace;
|
||||||
|
height: 300px;
|
||||||
|
color: black;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PADDING */
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
padding: 4px 0; /* Vertical padding around content */
|
||||||
|
}
|
||||||
|
.CodeMirror pre.CodeMirror-line,
|
||||||
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
padding: 0 4px; /* Horizontal padding of content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
background-color: white; /* The little square between H and V scrollbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GUTTER */
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.CodeMirror-linenumbers {}
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
padding: 0 3px 0 5px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: right;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-guttermarker { color: black; }
|
||||||
|
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||||
|
|
||||||
|
/* CURSOR */
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid black;
|
||||||
|
border-right: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
/* Shown when moving in bi-directional text */
|
||||||
|
.CodeMirror div.CodeMirror-secondarycursor {
|
||||||
|
border-left: 1px solid silver;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0 !important;
|
||||||
|
background: #7e7;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor div.CodeMirror-cursors {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-line::selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span::selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
|
||||||
|
.cm-fat-cursor .CodeMirror-line::-moz-selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
|
||||||
|
.cm-fat-cursor { caret-color: transparent; }
|
||||||
|
@-moz-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can style cursor different in overwrite (non-insert) mode */
|
||||||
|
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||||
|
|
||||||
|
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||||
|
|
||||||
|
.CodeMirror-rulers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: -50px; bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.CodeMirror-ruler {
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEFAULT THEME */
|
||||||
|
|
||||||
|
.cm-s-default .cm-header {color: blue;}
|
||||||
|
.cm-s-default .cm-quote {color: #090;}
|
||||||
|
.cm-negative {color: #d44;}
|
||||||
|
.cm-positive {color: #292;}
|
||||||
|
.cm-header, .cm-strong {font-weight: bold;}
|
||||||
|
.cm-em {font-style: italic;}
|
||||||
|
.cm-link {text-decoration: underline;}
|
||||||
|
.cm-strikethrough {text-decoration: line-through;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-keyword {color: #708;}
|
||||||
|
.cm-s-default .cm-atom {color: #219;}
|
||||||
|
.cm-s-default .cm-number {color: #164;}
|
||||||
|
.cm-s-default .cm-def {color: #00f;}
|
||||||
|
.cm-s-default .cm-variable,
|
||||||
|
.cm-s-default .cm-punctuation,
|
||||||
|
.cm-s-default .cm-property,
|
||||||
|
.cm-s-default .cm-operator {}
|
||||||
|
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||||
|
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||||
|
.cm-s-default .cm-comment {color: #a50;}
|
||||||
|
.cm-s-default .cm-string {color: #a11;}
|
||||||
|
.cm-s-default .cm-string-2 {color: #f50;}
|
||||||
|
.cm-s-default .cm-meta {color: #555;}
|
||||||
|
.cm-s-default .cm-qualifier {color: #555;}
|
||||||
|
.cm-s-default .cm-builtin {color: #30a;}
|
||||||
|
.cm-s-default .cm-bracket {color: #997;}
|
||||||
|
.cm-s-default .cm-tag {color: #170;}
|
||||||
|
.cm-s-default .cm-attribute {color: #00c;}
|
||||||
|
.cm-s-default .cm-hr {color: #999;}
|
||||||
|
.cm-s-default .cm-link {color: #00c;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-error {color: #f00;}
|
||||||
|
.cm-invalidchar {color: #f00;}
|
||||||
|
|
||||||
|
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||||
|
|
||||||
|
/* Default styles for common addons */
|
||||||
|
|
||||||
|
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||||
|
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||||
|
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||||
|
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||||
|
|
||||||
|
/* STOP */
|
||||||
|
|
||||||
|
/* The rest of this file contains styles related to the mechanics of
|
||||||
|
the editor. You probably shouldn't touch them. */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
overflow: scroll !important; /* Things will break if this is overridden */
|
||||||
|
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||||
|
/* See overflow: hidden in .CodeMirror */
|
||||||
|
margin-bottom: -50px; margin-right: -50px;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
height: 100%;
|
||||||
|
outline: none; /* Prevent dragging from highlighting the element */
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-sizer {
|
||||||
|
position: relative;
|
||||||
|
border-right: 50px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||||
|
before actual scrolling happens, thus preventing shaking and
|
||||||
|
flickering artifacts. */
|
||||||
|
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 6;
|
||||||
|
display: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-vscrollbar {
|
||||||
|
right: 0; top: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-hscrollbar {
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-scrollbar-filler {
|
||||||
|
right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-filler {
|
||||||
|
left: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
position: absolute; left: 0; top: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter {
|
||||||
|
white-space: normal;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-bottom: -50px;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 4;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-elt {
|
||||||
|
position: absolute;
|
||||||
|
cursor: default;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||||
|
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
cursor: text;
|
||||||
|
min-height: 1px; /* prevents collapsing before first draw */
|
||||||
|
}
|
||||||
|
.CodeMirror pre.CodeMirror-line,
|
||||||
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
/* Reset some styles that the rest of the page might have set */
|
||||||
|
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||||
|
border-width: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-font-variant-ligatures: contextual;
|
||||||
|
font-variant-ligatures: contextual;
|
||||||
|
}
|
||||||
|
.CodeMirror-wrap pre.CodeMirror-line,
|
||||||
|
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linebackground {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linewidget {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-widget {}
|
||||||
|
|
||||||
|
.CodeMirror-rtl pre { direction: rtl; }
|
||||||
|
|
||||||
|
.CodeMirror-code {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force content-box sizing for the elements where we expect it */
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-sizer,
|
||||||
|
.CodeMirror-gutter,
|
||||||
|
.CodeMirror-gutters,
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-measure {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-measure pre { position: static; }
|
||||||
|
|
||||||
|
div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
div.CodeMirror-dragcursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-focused div.CodeMirror-cursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-selected { background: #d9d9d9; }
|
||||||
|
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||||
|
.CodeMirror-crosshair { cursor: crosshair; }
|
||||||
|
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||||
|
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||||
|
|
||||||
|
.cm-searching {
|
||||||
|
background-color: #ffa;
|
||||||
|
background-color: rgba(255, 255, 0, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to force a border model for a node */
|
||||||
|
.cm-force-border { padding-right: .1px; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Hide the cursor when printing */
|
||||||
|
.CodeMirror div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See issue #2901 */
|
||||||
|
.cm-tab-wrap-hack:after { content: ''; }
|
||||||
|
|
||||||
|
/* Help users use markselection to safely style text background */
|
||||||
|
span.CodeMirror-selectedtext { background: none; }
|
||||||
9874
web/assets/codemirror/codemirror.js
Normal file
9874
web/assets/codemirror/codemirror.js
Normal file
File diff suppressed because it is too large
Load Diff
119
web/assets/codemirror/fold/brace-fold.js
Normal file
119
web/assets/codemirror/fold/brace-fold.js
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function bracketFolding(pairs) {
|
||||||
|
return function(cm, start) {
|
||||||
|
var line = start.line, lineText = cm.getLine(line);
|
||||||
|
|
||||||
|
function findOpening(pair) {
|
||||||
|
var tokenType;
|
||||||
|
for (var at = start.ch, pass = 0;;) {
|
||||||
|
var found = at <= 0 ? -1 : lineText.lastIndexOf(pair[0], at - 1);
|
||||||
|
if (found == -1) {
|
||||||
|
if (pass == 1) break;
|
||||||
|
pass = 1;
|
||||||
|
at = lineText.length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pass == 1 && found < start.ch) break;
|
||||||
|
tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
|
||||||
|
if (!/^(comment|string)/.test(tokenType)) return {ch: found + 1, tokenType: tokenType, pair: pair};
|
||||||
|
at = found - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRange(found) {
|
||||||
|
var count = 1, lastLine = cm.lastLine(), end, startCh = found.ch, endCh
|
||||||
|
outer: for (var i = line; i <= lastLine; ++i) {
|
||||||
|
var text = cm.getLine(i), pos = i == line ? startCh : 0;
|
||||||
|
for (;;) {
|
||||||
|
var nextOpen = text.indexOf(found.pair[0], pos), nextClose = text.indexOf(found.pair[1], pos);
|
||||||
|
if (nextOpen < 0) nextOpen = text.length;
|
||||||
|
if (nextClose < 0) nextClose = text.length;
|
||||||
|
pos = Math.min(nextOpen, nextClose);
|
||||||
|
if (pos == text.length) break;
|
||||||
|
if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == found.tokenType) {
|
||||||
|
if (pos == nextOpen) ++count;
|
||||||
|
else if (!--count) { end = i; endCh = pos; break outer; }
|
||||||
|
}
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end == null || line == end) return null
|
||||||
|
return {from: CodeMirror.Pos(line, startCh),
|
||||||
|
to: CodeMirror.Pos(end, endCh)};
|
||||||
|
}
|
||||||
|
|
||||||
|
var found = []
|
||||||
|
for (var i = 0; i < pairs.length; i++) {
|
||||||
|
var open = findOpening(pairs[i])
|
||||||
|
if (open) found.push(open)
|
||||||
|
}
|
||||||
|
found.sort(function(a, b) { return a.ch - b.ch })
|
||||||
|
for (var i = 0; i < found.length; i++) {
|
||||||
|
var range = findRange(found[i])
|
||||||
|
if (range) return range
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "brace", bracketFolding([["{", "}"], ["[", "]"]]));
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "brace-paren", bracketFolding([["{", "}"], ["[", "]"], ["(", ")"]]));
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "import", function(cm, start) {
|
||||||
|
function hasImport(line) {
|
||||||
|
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
||||||
|
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
||||||
|
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
||||||
|
if (start.type != "keyword" || start.string != "import") return null;
|
||||||
|
// Now find closing semicolon, return its position
|
||||||
|
for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
|
||||||
|
var text = cm.getLine(i), semi = text.indexOf(";");
|
||||||
|
if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startLine = start.line, has = hasImport(startLine), prev;
|
||||||
|
if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
|
||||||
|
return null;
|
||||||
|
for (var end = has.end;;) {
|
||||||
|
var next = hasImport(end.line + 1);
|
||||||
|
if (next == null) break;
|
||||||
|
end = next.end;
|
||||||
|
}
|
||||||
|
return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "include", function(cm, start) {
|
||||||
|
function hasInclude(line) {
|
||||||
|
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
||||||
|
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
||||||
|
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
||||||
|
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startLine = start.line, has = hasInclude(startLine);
|
||||||
|
if (has == null || hasInclude(startLine - 1) != null) return null;
|
||||||
|
for (var end = startLine;;) {
|
||||||
|
var next = hasInclude(end + 1);
|
||||||
|
if (next == null) break;
|
||||||
|
++end;
|
||||||
|
}
|
||||||
|
return {from: CodeMirror.Pos(startLine, has + 1),
|
||||||
|
to: cm.clipPos(CodeMirror.Pos(end))};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
159
web/assets/codemirror/fold/foldcode.js
Normal file
159
web/assets/codemirror/fold/foldcode.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function doFold(cm, pos, options, force) {
|
||||||
|
if (options && options.call) {
|
||||||
|
var finder = options;
|
||||||
|
options = null;
|
||||||
|
} else {
|
||||||
|
var finder = getOption(cm, options, "rangeFinder");
|
||||||
|
}
|
||||||
|
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
|
||||||
|
var minSize = getOption(cm, options, "minFoldSize");
|
||||||
|
|
||||||
|
function getRange(allowFolded) {
|
||||||
|
var range = finder(cm, pos);
|
||||||
|
if (!range || range.to.line - range.from.line < minSize) return null;
|
||||||
|
if (force === "fold") return range;
|
||||||
|
|
||||||
|
var marks = cm.findMarksAt(range.from);
|
||||||
|
for (var i = 0; i < marks.length; ++i) {
|
||||||
|
if (marks[i].__isFold) {
|
||||||
|
if (!allowFolded) return null;
|
||||||
|
range.cleared = true;
|
||||||
|
marks[i].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
var range = getRange(true);
|
||||||
|
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
|
||||||
|
pos = CodeMirror.Pos(pos.line - 1, 0);
|
||||||
|
range = getRange(false);
|
||||||
|
}
|
||||||
|
if (!range || range.cleared || force === "unfold") return;
|
||||||
|
|
||||||
|
var myWidget = makeWidget(cm, options, range);
|
||||||
|
CodeMirror.on(myWidget, "mousedown", function(e) {
|
||||||
|
myRange.clear();
|
||||||
|
CodeMirror.e_preventDefault(e);
|
||||||
|
});
|
||||||
|
var myRange = cm.markText(range.from, range.to, {
|
||||||
|
replacedWith: myWidget,
|
||||||
|
clearOnEnter: getOption(cm, options, "clearOnEnter"),
|
||||||
|
__isFold: true
|
||||||
|
});
|
||||||
|
myRange.on("clear", function(from, to) {
|
||||||
|
CodeMirror.signal(cm, "unfold", cm, from, to);
|
||||||
|
});
|
||||||
|
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeWidget(cm, options, range) {
|
||||||
|
var widget = getOption(cm, options, "widget");
|
||||||
|
|
||||||
|
if (typeof widget == "function") {
|
||||||
|
widget = widget(range.from, range.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof widget == "string") {
|
||||||
|
var text = document.createTextNode(widget);
|
||||||
|
widget = document.createElement("span");
|
||||||
|
widget.appendChild(text);
|
||||||
|
widget.className = "CodeMirror-foldmarker";
|
||||||
|
} else if (widget) {
|
||||||
|
widget = widget.cloneNode(true)
|
||||||
|
}
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clumsy backwards-compatible interface
|
||||||
|
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
|
||||||
|
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
|
||||||
|
};
|
||||||
|
|
||||||
|
// New-style interface
|
||||||
|
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
|
||||||
|
doFold(this, pos, options, force);
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("isFolded", function(pos) {
|
||||||
|
var marks = this.findMarksAt(pos);
|
||||||
|
for (var i = 0; i < marks.length; ++i)
|
||||||
|
if (marks[i].__isFold) return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.commands.toggleFold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor());
|
||||||
|
};
|
||||||
|
CodeMirror.commands.fold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor(), null, "fold");
|
||||||
|
};
|
||||||
|
CodeMirror.commands.unfold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor(), { scanUp: false }, "unfold");
|
||||||
|
};
|
||||||
|
CodeMirror.commands.foldAll = function(cm) {
|
||||||
|
cm.operation(function() {
|
||||||
|
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
||||||
|
cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "fold");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
CodeMirror.commands.unfoldAll = function(cm) {
|
||||||
|
cm.operation(function() {
|
||||||
|
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
||||||
|
cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "unfold");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "combine", function() {
|
||||||
|
var funcs = Array.prototype.slice.call(arguments, 0);
|
||||||
|
return function(cm, start) {
|
||||||
|
for (var i = 0; i < funcs.length; ++i) {
|
||||||
|
var found = funcs[i](cm, start);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
|
||||||
|
var helpers = cm.getHelpers(start, "fold");
|
||||||
|
for (var i = 0; i < helpers.length; i++) {
|
||||||
|
var cur = helpers[i](cm, start);
|
||||||
|
if (cur) return cur;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var defaultOptions = {
|
||||||
|
rangeFinder: CodeMirror.fold.auto,
|
||||||
|
widget: "\u2194",
|
||||||
|
minFoldSize: 0,
|
||||||
|
scanUp: false,
|
||||||
|
clearOnEnter: true
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.defineOption("foldOptions", null);
|
||||||
|
|
||||||
|
function getOption(cm, options, name) {
|
||||||
|
if (options && options[name] !== undefined)
|
||||||
|
return options[name];
|
||||||
|
var editorOptions = cm.options.foldOptions;
|
||||||
|
if (editorOptions && editorOptions[name] !== undefined)
|
||||||
|
return editorOptions[name];
|
||||||
|
return defaultOptions[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("foldOption", function(options, name) {
|
||||||
|
return getOption(this, options, name);
|
||||||
|
});
|
||||||
|
});
|
||||||
20
web/assets/codemirror/fold/foldgutter.css
Normal file
20
web/assets/codemirror/fold/foldgutter.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.CodeMirror-foldmarker {
|
||||||
|
color: blue;
|
||||||
|
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
|
||||||
|
font-family: arial;
|
||||||
|
line-height: .3;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter {
|
||||||
|
width: .7em;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-open,
|
||||||
|
.CodeMirror-foldgutter-folded {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-open:after {
|
||||||
|
content: "\25BE";
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-folded:after {
|
||||||
|
content: "\25B8";
|
||||||
|
}
|
||||||
169
web/assets/codemirror/fold/foldgutter.js
Normal file
169
web/assets/codemirror/fold/foldgutter.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"), require("./foldcode"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "./foldcode"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
|
||||||
|
if (old && old != CodeMirror.Init) {
|
||||||
|
cm.clearGutter(cm.state.foldGutter.options.gutter);
|
||||||
|
cm.state.foldGutter = null;
|
||||||
|
cm.off("gutterClick", onGutterClick);
|
||||||
|
cm.off("changes", onChange);
|
||||||
|
cm.off("viewportChange", onViewportChange);
|
||||||
|
cm.off("fold", onFold);
|
||||||
|
cm.off("unfold", onFold);
|
||||||
|
cm.off("swapDoc", onChange);
|
||||||
|
cm.off("optionChange", optionChange);
|
||||||
|
}
|
||||||
|
if (val) {
|
||||||
|
cm.state.foldGutter = new State(parseOptions(val));
|
||||||
|
updateInViewport(cm);
|
||||||
|
cm.on("gutterClick", onGutterClick);
|
||||||
|
cm.on("changes", onChange);
|
||||||
|
cm.on("viewportChange", onViewportChange);
|
||||||
|
cm.on("fold", onFold);
|
||||||
|
cm.on("unfold", onFold);
|
||||||
|
cm.on("swapDoc", onChange);
|
||||||
|
cm.on("optionChange", optionChange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Pos = CodeMirror.Pos;
|
||||||
|
|
||||||
|
function State(options) {
|
||||||
|
this.options = options;
|
||||||
|
this.from = this.to = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptions(opts) {
|
||||||
|
if (opts === true) opts = {};
|
||||||
|
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
|
||||||
|
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
|
||||||
|
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFolded(cm, line) {
|
||||||
|
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
|
||||||
|
for (var i = 0; i < marks.length; ++i) {
|
||||||
|
if (marks[i].__isFold) {
|
||||||
|
var fromPos = marks[i].find(-1);
|
||||||
|
if (fromPos && fromPos.line === line)
|
||||||
|
return marks[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function marker(spec) {
|
||||||
|
if (typeof spec == "string") {
|
||||||
|
var elt = document.createElement("div");
|
||||||
|
elt.className = spec + " CodeMirror-guttermarker-subtle";
|
||||||
|
return elt;
|
||||||
|
} else {
|
||||||
|
return spec.cloneNode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFoldInfo(cm, from, to) {
|
||||||
|
var opts = cm.state.foldGutter.options, cur = from - 1;
|
||||||
|
var minSize = cm.foldOption(opts, "minFoldSize");
|
||||||
|
var func = cm.foldOption(opts, "rangeFinder");
|
||||||
|
// we can reuse the built-in indicator element if its className matches the new state
|
||||||
|
var clsFolded = typeof opts.indicatorFolded == "string" && classTest(opts.indicatorFolded);
|
||||||
|
var clsOpen = typeof opts.indicatorOpen == "string" && classTest(opts.indicatorOpen);
|
||||||
|
cm.eachLine(from, to, function(line) {
|
||||||
|
++cur;
|
||||||
|
var mark = null;
|
||||||
|
var old = line.gutterMarkers;
|
||||||
|
if (old) old = old[opts.gutter];
|
||||||
|
if (isFolded(cm, cur)) {
|
||||||
|
if (clsFolded && old && clsFolded.test(old.className)) return;
|
||||||
|
mark = marker(opts.indicatorFolded);
|
||||||
|
} else {
|
||||||
|
var pos = Pos(cur, 0);
|
||||||
|
var range = func && func(cm, pos);
|
||||||
|
if (range && range.to.line - range.from.line >= minSize) {
|
||||||
|
if (clsOpen && old && clsOpen.test(old.className)) return;
|
||||||
|
mark = marker(opts.indicatorOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mark && !old) return;
|
||||||
|
cm.setGutterMarker(line, opts.gutter, mark);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from CodeMirror/src/util/dom.js
|
||||||
|
function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
|
||||||
|
|
||||||
|
function updateInViewport(cm) {
|
||||||
|
var vp = cm.getViewport(), state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
cm.operation(function() {
|
||||||
|
updateFoldInfo(cm, vp.from, vp.to);
|
||||||
|
});
|
||||||
|
state.from = vp.from; state.to = vp.to;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGutterClick(cm, line, gutter) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
if (gutter != opts.gutter) return;
|
||||||
|
var folded = isFolded(cm, line);
|
||||||
|
if (folded) folded.clear();
|
||||||
|
else cm.foldCode(Pos(line, 0), opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionChange(cm, option) {
|
||||||
|
if (option == "mode") onChange(cm)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(cm) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
state.from = state.to = 0;
|
||||||
|
clearTimeout(state.changeUpdate);
|
||||||
|
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onViewportChange(cm) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
clearTimeout(state.changeUpdate);
|
||||||
|
state.changeUpdate = setTimeout(function() {
|
||||||
|
var vp = cm.getViewport();
|
||||||
|
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
|
||||||
|
updateInViewport(cm);
|
||||||
|
} else {
|
||||||
|
cm.operation(function() {
|
||||||
|
if (vp.from < state.from) {
|
||||||
|
updateFoldInfo(cm, vp.from, state.from);
|
||||||
|
state.from = vp.from;
|
||||||
|
}
|
||||||
|
if (vp.to > state.to) {
|
||||||
|
updateFoldInfo(cm, state.to, vp.to);
|
||||||
|
state.to = vp.to;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, opts.updateViewportTimeSpan || 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFold(cm, from) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var line = from.line;
|
||||||
|
if (line >= state.from && line < state.to)
|
||||||
|
updateFoldInfo(cm, line, line + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
162
web/assets/codemirror/hint/javascript-hint.js
Normal file
162
web/assets/codemirror/hint/javascript-hint.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
var Pos = CodeMirror.Pos;
|
||||||
|
|
||||||
|
function forEach(arr, f) {
|
||||||
|
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayContains(arr, item) {
|
||||||
|
if (!Array.prototype.indexOf) {
|
||||||
|
var i = arr.length;
|
||||||
|
while (i--) {
|
||||||
|
if (arr[i] === item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return arr.indexOf(item) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scriptHint(editor, keywords, getToken, options) {
|
||||||
|
// Find the token at the cursor
|
||||||
|
var cur = editor.getCursor(), token = getToken(editor, cur);
|
||||||
|
if (/\b(?:string|comment)\b/.test(token.type)) return;
|
||||||
|
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
|
||||||
|
if (innerMode.mode.helperType === "json") return;
|
||||||
|
token.state = innerMode.state;
|
||||||
|
|
||||||
|
// If it's not a 'word-style' token, ignore the token.
|
||||||
|
if (!/^[\w$_]*$/.test(token.string)) {
|
||||||
|
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
|
||||||
|
type: token.string == "." ? "property" : null};
|
||||||
|
} else if (token.end > cur.ch) {
|
||||||
|
token.end = cur.ch;
|
||||||
|
token.string = token.string.slice(0, cur.ch - token.start);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tprop = token;
|
||||||
|
// If it is a property, find out what it is a property of.
|
||||||
|
while (tprop.type == "property") {
|
||||||
|
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
||||||
|
if (tprop.string != ".") return;
|
||||||
|
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
||||||
|
if (!context) var context = [];
|
||||||
|
context.push(tprop);
|
||||||
|
}
|
||||||
|
return {list: getCompletions(token, context, keywords, options),
|
||||||
|
from: Pos(cur.line, token.start),
|
||||||
|
to: Pos(cur.line, token.end)};
|
||||||
|
}
|
||||||
|
|
||||||
|
function javascriptHint(editor, options) {
|
||||||
|
return scriptHint(editor, javascriptKeywords,
|
||||||
|
function (e, cur) {return e.getTokenAt(cur);},
|
||||||
|
options);
|
||||||
|
};
|
||||||
|
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
|
||||||
|
|
||||||
|
function getCoffeeScriptToken(editor, cur) {
|
||||||
|
// This getToken, it is for coffeescript, imitates the behavior of
|
||||||
|
// getTokenAt method in javascript.js, that is, returning "property"
|
||||||
|
// type and treat "." as independent token.
|
||||||
|
var token = editor.getTokenAt(cur);
|
||||||
|
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
|
||||||
|
token.end = token.start;
|
||||||
|
token.string = '.';
|
||||||
|
token.type = "property";
|
||||||
|
}
|
||||||
|
else if (/^\.[\w$_]*$/.test(token.string)) {
|
||||||
|
token.type = "property";
|
||||||
|
token.start++;
|
||||||
|
token.string = token.string.replace(/\./, '');
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function coffeescriptHint(editor, options) {
|
||||||
|
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
|
||||||
|
}
|
||||||
|
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
|
||||||
|
|
||||||
|
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
|
||||||
|
"toUpperCase toLowerCase split concat match replace search").split(" ");
|
||||||
|
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
|
||||||
|
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
|
||||||
|
var funcProps = "prototype apply call bind".split(" ");
|
||||||
|
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
|
||||||
|
"if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" ");
|
||||||
|
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
|
||||||
|
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
|
||||||
|
|
||||||
|
function forAllProps(obj, callback) {
|
||||||
|
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
|
||||||
|
for (var name in obj) callback(name)
|
||||||
|
} else {
|
||||||
|
for (var o = obj; o; o = Object.getPrototypeOf(o))
|
||||||
|
Object.getOwnPropertyNames(o).forEach(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCompletions(token, context, keywords, options) {
|
||||||
|
var found = [], start = token.string, global = options && options.globalScope || window;
|
||||||
|
function maybeAdd(str) {
|
||||||
|
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
|
||||||
|
}
|
||||||
|
function gatherCompletions(obj) {
|
||||||
|
if (typeof obj == "string") forEach(stringProps, maybeAdd);
|
||||||
|
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
|
||||||
|
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
|
||||||
|
forAllProps(obj, maybeAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context.length) {
|
||||||
|
// If this is a property, see if it belongs to some object we can
|
||||||
|
// find in the current environment.
|
||||||
|
var obj = context.pop(), base;
|
||||||
|
if (obj.type && obj.type.indexOf("variable") === 0) {
|
||||||
|
if (options && options.additionalContext)
|
||||||
|
base = options.additionalContext[obj.string];
|
||||||
|
if (!options || options.useGlobalScope !== false)
|
||||||
|
base = base || global[obj.string];
|
||||||
|
} else if (obj.type == "string") {
|
||||||
|
base = "";
|
||||||
|
} else if (obj.type == "atom") {
|
||||||
|
base = 1;
|
||||||
|
} else if (obj.type == "function") {
|
||||||
|
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
|
||||||
|
(typeof global.jQuery == 'function'))
|
||||||
|
base = global.jQuery();
|
||||||
|
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
|
||||||
|
base = global._();
|
||||||
|
}
|
||||||
|
while (base != null && context.length)
|
||||||
|
base = base[context.pop().string];
|
||||||
|
if (base != null) gatherCompletions(base);
|
||||||
|
} else {
|
||||||
|
// If not, just look in the global object, any local scope, and optional additional-context
|
||||||
|
// (reading into JS mode internals to get at the local and global variables)
|
||||||
|
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
|
||||||
|
for (var c = token.state.context; c; c = c.prev)
|
||||||
|
for (var v = c.vars; v; v = v.next) maybeAdd(v.name)
|
||||||
|
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
|
||||||
|
if (options && options.additionalContext != null)
|
||||||
|
for (var key in options.additionalContext)
|
||||||
|
maybeAdd(key);
|
||||||
|
if (!options || options.useGlobalScope !== false)
|
||||||
|
gatherCompletions(global);
|
||||||
|
forEach(keywords, maybeAdd);
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
});
|
||||||
960
web/assets/codemirror/javascript.js
Normal file
960
web/assets/codemirror/javascript.js
Normal file
@@ -0,0 +1,960 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||||
|
var indentUnit = config.indentUnit;
|
||||||
|
var statementIndent = parserConfig.statementIndent;
|
||||||
|
var jsonldMode = parserConfig.jsonld;
|
||||||
|
var jsonMode = parserConfig.json || jsonldMode;
|
||||||
|
var trackScope = parserConfig.trackScope !== false
|
||||||
|
var isTS = parserConfig.typescript;
|
||||||
|
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
|
||||||
|
|
||||||
|
// Tokenizer
|
||||||
|
|
||||||
|
var keywords = function(){
|
||||||
|
function kw(type) {return {type: type, style: "keyword"};}
|
||||||
|
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
|
||||||
|
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
|
||||||
|
|
||||||
|
return {
|
||||||
|
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
|
||||||
|
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
|
||||||
|
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
|
||||||
|
"function": kw("function"), "catch": kw("catch"),
|
||||||
|
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
|
||||||
|
"in": operator, "typeof": operator, "instanceof": operator,
|
||||||
|
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
|
||||||
|
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
|
||||||
|
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
|
||||||
|
"await": C
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
|
||||||
|
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
|
||||||
|
|
||||||
|
function readRegexp(stream) {
|
||||||
|
var escaped = false, next, inSet = false;
|
||||||
|
while ((next = stream.next()) != null) {
|
||||||
|
if (!escaped) {
|
||||||
|
if (next == "/" && !inSet) return;
|
||||||
|
if (next == "[") inSet = true;
|
||||||
|
else if (inSet && next == "]") inSet = false;
|
||||||
|
}
|
||||||
|
escaped = !escaped && next == "\\";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used as scratch variables to communicate multiple values without
|
||||||
|
// consing up tons of objects.
|
||||||
|
var type, content;
|
||||||
|
function ret(tp, style, cont) {
|
||||||
|
type = tp; content = cont;
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
function tokenBase(stream, state) {
|
||||||
|
var ch = stream.next();
|
||||||
|
if (ch == '"' || ch == "'") {
|
||||||
|
state.tokenize = tokenString(ch);
|
||||||
|
return state.tokenize(stream, state);
|
||||||
|
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
|
||||||
|
return ret("number", "number");
|
||||||
|
} else if (ch == "." && stream.match("..")) {
|
||||||
|
return ret("spread", "meta");
|
||||||
|
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
|
||||||
|
return ret(ch);
|
||||||
|
} else if (ch == "=" && stream.eat(">")) {
|
||||||
|
return ret("=>", "operator");
|
||||||
|
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
|
||||||
|
return ret("number", "number");
|
||||||
|
} else if (/\d/.test(ch)) {
|
||||||
|
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
|
||||||
|
return ret("number", "number");
|
||||||
|
} else if (ch == "/") {
|
||||||
|
if (stream.eat("*")) {
|
||||||
|
state.tokenize = tokenComment;
|
||||||
|
return tokenComment(stream, state);
|
||||||
|
} else if (stream.eat("/")) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return ret("comment", "comment");
|
||||||
|
} else if (expressionAllowed(stream, state, 1)) {
|
||||||
|
readRegexp(stream);
|
||||||
|
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
|
||||||
|
return ret("regexp", "string-2");
|
||||||
|
} else {
|
||||||
|
stream.eat("=");
|
||||||
|
return ret("operator", "operator", stream.current());
|
||||||
|
}
|
||||||
|
} else if (ch == "`") {
|
||||||
|
state.tokenize = tokenQuasi;
|
||||||
|
return tokenQuasi(stream, state);
|
||||||
|
} else if (ch == "#" && stream.peek() == "!") {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return ret("meta", "meta");
|
||||||
|
} else if (ch == "#" && stream.eatWhile(wordRE)) {
|
||||||
|
return ret("variable", "property")
|
||||||
|
} else if (ch == "<" && stream.match("!--") ||
|
||||||
|
(ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) {
|
||||||
|
stream.skipToEnd()
|
||||||
|
return ret("comment", "comment")
|
||||||
|
} else if (isOperatorChar.test(ch)) {
|
||||||
|
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
|
||||||
|
if (stream.eat("=")) {
|
||||||
|
if (ch == "!" || ch == "=") stream.eat("=")
|
||||||
|
} else if (/[<>*+\-|&?]/.test(ch)) {
|
||||||
|
stream.eat(ch)
|
||||||
|
if (ch == ">") stream.eat(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch == "?" && stream.eat(".")) return ret(".")
|
||||||
|
return ret("operator", "operator", stream.current());
|
||||||
|
} else if (wordRE.test(ch)) {
|
||||||
|
stream.eatWhile(wordRE);
|
||||||
|
var word = stream.current()
|
||||||
|
if (state.lastType != ".") {
|
||||||
|
if (keywords.propertyIsEnumerable(word)) {
|
||||||
|
var kw = keywords[word]
|
||||||
|
return ret(kw.type, kw.style, word)
|
||||||
|
}
|
||||||
|
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
|
||||||
|
return ret("async", "keyword", word)
|
||||||
|
}
|
||||||
|
return ret("variable", "variable", word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenString(quote) {
|
||||||
|
return function(stream, state) {
|
||||||
|
var escaped = false, next;
|
||||||
|
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
|
||||||
|
state.tokenize = tokenBase;
|
||||||
|
return ret("jsonld-keyword", "meta");
|
||||||
|
}
|
||||||
|
while ((next = stream.next()) != null) {
|
||||||
|
if (next == quote && !escaped) break;
|
||||||
|
escaped = !escaped && next == "\\";
|
||||||
|
}
|
||||||
|
if (!escaped) state.tokenize = tokenBase;
|
||||||
|
return ret("string", "string");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenComment(stream, state) {
|
||||||
|
var maybeEnd = false, ch;
|
||||||
|
while (ch = stream.next()) {
|
||||||
|
if (ch == "/" && maybeEnd) {
|
||||||
|
state.tokenize = tokenBase;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
maybeEnd = (ch == "*");
|
||||||
|
}
|
||||||
|
return ret("comment", "comment");
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenQuasi(stream, state) {
|
||||||
|
var escaped = false, next;
|
||||||
|
while ((next = stream.next()) != null) {
|
||||||
|
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
|
||||||
|
state.tokenize = tokenBase;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
escaped = !escaped && next == "\\";
|
||||||
|
}
|
||||||
|
return ret("quasi", "string-2", stream.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
var brackets = "([{}])";
|
||||||
|
// This is a crude lookahead trick to try and notice that we're
|
||||||
|
// parsing the argument patterns for a fat-arrow function before we
|
||||||
|
// actually hit the arrow token. It only works if the arrow is on
|
||||||
|
// the same line as the arguments and there's no strange noise
|
||||||
|
// (comments) in between. Fallback is to only notice when we hit the
|
||||||
|
// arrow, and not declare the arguments as locals for the arrow
|
||||||
|
// body.
|
||||||
|
function findFatArrow(stream, state) {
|
||||||
|
if (state.fatArrowAt) state.fatArrowAt = null;
|
||||||
|
var arrow = stream.string.indexOf("=>", stream.start);
|
||||||
|
if (arrow < 0) return;
|
||||||
|
|
||||||
|
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
|
||||||
|
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
|
||||||
|
if (m) arrow = m.index
|
||||||
|
}
|
||||||
|
|
||||||
|
var depth = 0, sawSomething = false;
|
||||||
|
for (var pos = arrow - 1; pos >= 0; --pos) {
|
||||||
|
var ch = stream.string.charAt(pos);
|
||||||
|
var bracket = brackets.indexOf(ch);
|
||||||
|
if (bracket >= 0 && bracket < 3) {
|
||||||
|
if (!depth) { ++pos; break; }
|
||||||
|
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
|
||||||
|
} else if (bracket >= 3 && bracket < 6) {
|
||||||
|
++depth;
|
||||||
|
} else if (wordRE.test(ch)) {
|
||||||
|
sawSomething = true;
|
||||||
|
} else if (/["'\/`]/.test(ch)) {
|
||||||
|
for (;; --pos) {
|
||||||
|
if (pos == 0) return
|
||||||
|
var next = stream.string.charAt(pos - 1)
|
||||||
|
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
|
||||||
|
}
|
||||||
|
} else if (sawSomething && !depth) {
|
||||||
|
++pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sawSomething && !depth) state.fatArrowAt = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser
|
||||||
|
|
||||||
|
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true,
|
||||||
|
"regexp": true, "this": true, "import": true, "jsonld-keyword": true};
|
||||||
|
|
||||||
|
function JSLexical(indented, column, type, align, prev, info) {
|
||||||
|
this.indented = indented;
|
||||||
|
this.column = column;
|
||||||
|
this.type = type;
|
||||||
|
this.prev = prev;
|
||||||
|
this.info = info;
|
||||||
|
if (align != null) this.align = align;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inScope(state, varname) {
|
||||||
|
if (!trackScope) return false
|
||||||
|
for (var v = state.localVars; v; v = v.next)
|
||||||
|
if (v.name == varname) return true;
|
||||||
|
for (var cx = state.context; cx; cx = cx.prev) {
|
||||||
|
for (var v = cx.vars; v; v = v.next)
|
||||||
|
if (v.name == varname) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJS(state, style, type, content, stream) {
|
||||||
|
var cc = state.cc;
|
||||||
|
// Communicate our context to the combinators.
|
||||||
|
// (Less wasteful than consing up a hundred closures on every call.)
|
||||||
|
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
|
||||||
|
|
||||||
|
if (!state.lexical.hasOwnProperty("align"))
|
||||||
|
state.lexical.align = true;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
|
||||||
|
if (combinator(type, content)) {
|
||||||
|
while(cc.length && cc[cc.length - 1].lex)
|
||||||
|
cc.pop()();
|
||||||
|
if (cx.marked) return cx.marked;
|
||||||
|
if (type == "variable" && inScope(state, content)) return "variable-2";
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combinator utils
|
||||||
|
|
||||||
|
var cx = {state: null, column: null, marked: null, cc: null};
|
||||||
|
function pass() {
|
||||||
|
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
|
||||||
|
}
|
||||||
|
function cont() {
|
||||||
|
pass.apply(null, arguments);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function inList(name, list) {
|
||||||
|
for (var v = list; v; v = v.next) if (v.name == name) return true
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function register(varname) {
|
||||||
|
var state = cx.state;
|
||||||
|
cx.marked = "def";
|
||||||
|
if (!trackScope) return
|
||||||
|
if (state.context) {
|
||||||
|
if (state.lexical.info == "var" && state.context && state.context.block) {
|
||||||
|
// FIXME function decls are also not block scoped
|
||||||
|
var newContext = registerVarScoped(varname, state.context)
|
||||||
|
if (newContext != null) {
|
||||||
|
state.context = newContext
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (!inList(varname, state.localVars)) {
|
||||||
|
state.localVars = new Var(varname, state.localVars)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall through means this is global
|
||||||
|
if (parserConfig.globalVars && !inList(varname, state.globalVars))
|
||||||
|
state.globalVars = new Var(varname, state.globalVars)
|
||||||
|
}
|
||||||
|
function registerVarScoped(varname, context) {
|
||||||
|
if (!context) {
|
||||||
|
return null
|
||||||
|
} else if (context.block) {
|
||||||
|
var inner = registerVarScoped(varname, context.prev)
|
||||||
|
if (!inner) return null
|
||||||
|
if (inner == context.prev) return context
|
||||||
|
return new Context(inner, context.vars, true)
|
||||||
|
} else if (inList(varname, context.vars)) {
|
||||||
|
return context
|
||||||
|
} else {
|
||||||
|
return new Context(context.prev, new Var(varname, context.vars), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isModifier(name) {
|
||||||
|
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combinators
|
||||||
|
|
||||||
|
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
|
||||||
|
function Var(name, next) { this.name = name; this.next = next }
|
||||||
|
|
||||||
|
var defaultVars = new Var("this", new Var("arguments", null))
|
||||||
|
function pushcontext() {
|
||||||
|
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
|
||||||
|
cx.state.localVars = defaultVars
|
||||||
|
}
|
||||||
|
function pushblockcontext() {
|
||||||
|
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
|
||||||
|
cx.state.localVars = null
|
||||||
|
}
|
||||||
|
pushcontext.lex = pushblockcontext.lex = true
|
||||||
|
function popcontext() {
|
||||||
|
cx.state.localVars = cx.state.context.vars
|
||||||
|
cx.state.context = cx.state.context.prev
|
||||||
|
}
|
||||||
|
popcontext.lex = true
|
||||||
|
function pushlex(type, info) {
|
||||||
|
var result = function() {
|
||||||
|
var state = cx.state, indent = state.indented;
|
||||||
|
if (state.lexical.type == "stat") indent = state.lexical.indented;
|
||||||
|
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
|
||||||
|
indent = outer.indented;
|
||||||
|
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
|
||||||
|
};
|
||||||
|
result.lex = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function poplex() {
|
||||||
|
var state = cx.state;
|
||||||
|
if (state.lexical.prev) {
|
||||||
|
if (state.lexical.type == ")")
|
||||||
|
state.indented = state.lexical.indented;
|
||||||
|
state.lexical = state.lexical.prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
poplex.lex = true;
|
||||||
|
|
||||||
|
function expect(wanted) {
|
||||||
|
function exp(type) {
|
||||||
|
if (type == wanted) return cont();
|
||||||
|
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
|
||||||
|
else return cont(exp);
|
||||||
|
};
|
||||||
|
return exp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function statement(type, value) {
|
||||||
|
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
|
||||||
|
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
|
||||||
|
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
|
||||||
|
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
|
||||||
|
if (type == "debugger") return cont(expect(";"));
|
||||||
|
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
|
||||||
|
if (type == ";") return cont();
|
||||||
|
if (type == "if") {
|
||||||
|
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
|
||||||
|
cx.state.cc.pop()();
|
||||||
|
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
|
||||||
|
}
|
||||||
|
if (type == "function") return cont(functiondef);
|
||||||
|
if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
|
||||||
|
if (type == "class" || (isTS && value == "interface")) {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
|
||||||
|
}
|
||||||
|
if (type == "variable") {
|
||||||
|
if (isTS && value == "declare") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(statement)
|
||||||
|
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
if (value == "enum") return cont(enumdef);
|
||||||
|
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
|
||||||
|
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
|
||||||
|
} else if (isTS && value == "namespace") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(pushlex("form"), expression, statement, poplex)
|
||||||
|
} else if (isTS && value == "abstract") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(statement)
|
||||||
|
} else {
|
||||||
|
return cont(pushlex("stat"), maybelabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
|
||||||
|
block, poplex, poplex, popcontext);
|
||||||
|
if (type == "case") return cont(expression, expect(":"));
|
||||||
|
if (type == "default") return cont(expect(":"));
|
||||||
|
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
|
||||||
|
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
|
||||||
|
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
|
||||||
|
if (type == "async") return cont(statement)
|
||||||
|
if (value == "@") return cont(expression, statement)
|
||||||
|
return pass(pushlex("stat"), expression, expect(";"), poplex);
|
||||||
|
}
|
||||||
|
function maybeCatchBinding(type) {
|
||||||
|
if (type == "(") return cont(funarg, expect(")"))
|
||||||
|
}
|
||||||
|
function expression(type, value) {
|
||||||
|
return expressionInner(type, value, false);
|
||||||
|
}
|
||||||
|
function expressionNoComma(type, value) {
|
||||||
|
return expressionInner(type, value, true);
|
||||||
|
}
|
||||||
|
function parenExpr(type) {
|
||||||
|
if (type != "(") return pass()
|
||||||
|
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
|
||||||
|
}
|
||||||
|
function expressionInner(type, value, noComma) {
|
||||||
|
if (cx.state.fatArrowAt == cx.stream.start) {
|
||||||
|
var body = noComma ? arrowBodyNoComma : arrowBody;
|
||||||
|
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
|
||||||
|
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
|
||||||
|
}
|
||||||
|
|
||||||
|
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
|
||||||
|
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
|
||||||
|
if (type == "function") return cont(functiondef, maybeop);
|
||||||
|
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
|
||||||
|
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
|
||||||
|
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
|
||||||
|
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
|
||||||
|
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
|
||||||
|
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
|
||||||
|
if (type == "quasi") return pass(quasi, maybeop);
|
||||||
|
if (type == "new") return cont(maybeTarget(noComma));
|
||||||
|
return cont();
|
||||||
|
}
|
||||||
|
function maybeexpression(type) {
|
||||||
|
if (type.match(/[;\}\)\],]/)) return pass();
|
||||||
|
return pass(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeoperatorComma(type, value) {
|
||||||
|
if (type == ",") return cont(maybeexpression);
|
||||||
|
return maybeoperatorNoComma(type, value, false);
|
||||||
|
}
|
||||||
|
function maybeoperatorNoComma(type, value, noComma) {
|
||||||
|
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
|
||||||
|
var expr = noComma == false ? expression : expressionNoComma;
|
||||||
|
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
|
||||||
|
if (type == "operator") {
|
||||||
|
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
|
||||||
|
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
|
||||||
|
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
|
||||||
|
if (value == "?") return cont(expression, expect(":"), expr);
|
||||||
|
return cont(expr);
|
||||||
|
}
|
||||||
|
if (type == "quasi") { return pass(quasi, me); }
|
||||||
|
if (type == ";") return;
|
||||||
|
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
|
||||||
|
if (type == ".") return cont(property, me);
|
||||||
|
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
|
||||||
|
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
|
||||||
|
if (type == "regexp") {
|
||||||
|
cx.state.lastType = cx.marked = "operator"
|
||||||
|
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
|
||||||
|
return cont(expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function quasi(type, value) {
|
||||||
|
if (type != "quasi") return pass();
|
||||||
|
if (value.slice(value.length - 2) != "${") return cont(quasi);
|
||||||
|
return cont(maybeexpression, continueQuasi);
|
||||||
|
}
|
||||||
|
function continueQuasi(type) {
|
||||||
|
if (type == "}") {
|
||||||
|
cx.marked = "string-2";
|
||||||
|
cx.state.tokenize = tokenQuasi;
|
||||||
|
return cont(quasi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function arrowBody(type) {
|
||||||
|
findFatArrow(cx.stream, cx.state);
|
||||||
|
return pass(type == "{" ? statement : expression);
|
||||||
|
}
|
||||||
|
function arrowBodyNoComma(type) {
|
||||||
|
findFatArrow(cx.stream, cx.state);
|
||||||
|
return pass(type == "{" ? statement : expressionNoComma);
|
||||||
|
}
|
||||||
|
function maybeTarget(noComma) {
|
||||||
|
return function(type) {
|
||||||
|
if (type == ".") return cont(noComma ? targetNoComma : target);
|
||||||
|
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
|
||||||
|
else return pass(noComma ? expressionNoComma : expression);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function target(_, value) {
|
||||||
|
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
|
||||||
|
}
|
||||||
|
function targetNoComma(_, value) {
|
||||||
|
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
|
||||||
|
}
|
||||||
|
function maybelabel(type) {
|
||||||
|
if (type == ":") return cont(poplex, statement);
|
||||||
|
return pass(maybeoperatorComma, expect(";"), poplex);
|
||||||
|
}
|
||||||
|
function property(type) {
|
||||||
|
if (type == "variable") {cx.marked = "property"; return cont();}
|
||||||
|
}
|
||||||
|
function objprop(type, value) {
|
||||||
|
if (type == "async") {
|
||||||
|
cx.marked = "property";
|
||||||
|
return cont(objprop);
|
||||||
|
} else if (type == "variable" || cx.style == "keyword") {
|
||||||
|
cx.marked = "property";
|
||||||
|
if (value == "get" || value == "set") return cont(getterSetter);
|
||||||
|
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
|
||||||
|
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
|
||||||
|
cx.state.fatArrowAt = cx.stream.pos + m[0].length
|
||||||
|
return cont(afterprop);
|
||||||
|
} else if (type == "number" || type == "string") {
|
||||||
|
cx.marked = jsonldMode ? "property" : (cx.style + " property");
|
||||||
|
return cont(afterprop);
|
||||||
|
} else if (type == "jsonld-keyword") {
|
||||||
|
return cont(afterprop);
|
||||||
|
} else if (isTS && isModifier(value)) {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(objprop)
|
||||||
|
} else if (type == "[") {
|
||||||
|
return cont(expression, maybetype, expect("]"), afterprop);
|
||||||
|
} else if (type == "spread") {
|
||||||
|
return cont(expressionNoComma, afterprop);
|
||||||
|
} else if (value == "*") {
|
||||||
|
cx.marked = "keyword";
|
||||||
|
return cont(objprop);
|
||||||
|
} else if (type == ":") {
|
||||||
|
return pass(afterprop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getterSetter(type) {
|
||||||
|
if (type != "variable") return pass(afterprop);
|
||||||
|
cx.marked = "property";
|
||||||
|
return cont(functiondef);
|
||||||
|
}
|
||||||
|
function afterprop(type) {
|
||||||
|
if (type == ":") return cont(expressionNoComma);
|
||||||
|
if (type == "(") return pass(functiondef);
|
||||||
|
}
|
||||||
|
function commasep(what, end, sep) {
|
||||||
|
function proceed(type, value) {
|
||||||
|
if (sep ? sep.indexOf(type) > -1 : type == ",") {
|
||||||
|
var lex = cx.state.lexical;
|
||||||
|
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
|
||||||
|
return cont(function(type, value) {
|
||||||
|
if (type == end || value == end) return pass()
|
||||||
|
return pass(what)
|
||||||
|
}, proceed);
|
||||||
|
}
|
||||||
|
if (type == end || value == end) return cont();
|
||||||
|
if (sep && sep.indexOf(";") > -1) return pass(what)
|
||||||
|
return cont(expect(end));
|
||||||
|
}
|
||||||
|
return function(type, value) {
|
||||||
|
if (type == end || value == end) return cont();
|
||||||
|
return pass(what, proceed);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function contCommasep(what, end, info) {
|
||||||
|
for (var i = 3; i < arguments.length; i++)
|
||||||
|
cx.cc.push(arguments[i]);
|
||||||
|
return cont(pushlex(end, info), commasep(what, end), poplex);
|
||||||
|
}
|
||||||
|
function block(type) {
|
||||||
|
if (type == "}") return cont();
|
||||||
|
return pass(statement, block);
|
||||||
|
}
|
||||||
|
function maybetype(type, value) {
|
||||||
|
if (isTS) {
|
||||||
|
if (type == ":") return cont(typeexpr);
|
||||||
|
if (value == "?") return cont(maybetype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function maybetypeOrIn(type, value) {
|
||||||
|
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
|
||||||
|
}
|
||||||
|
function mayberettype(type) {
|
||||||
|
if (isTS && type == ":") {
|
||||||
|
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
|
||||||
|
else return cont(typeexpr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isKW(_, value) {
|
||||||
|
if (value == "is") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function typeexpr(type, value) {
|
||||||
|
if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(value == "typeof" ? expressionNoComma : typeexpr)
|
||||||
|
}
|
||||||
|
if (type == "variable" || value == "void") {
|
||||||
|
cx.marked = "type"
|
||||||
|
return cont(afterType)
|
||||||
|
}
|
||||||
|
if (value == "|" || value == "&") return cont(typeexpr)
|
||||||
|
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
|
||||||
|
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
|
||||||
|
if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType)
|
||||||
|
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
|
||||||
|
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
|
||||||
|
if (type == "quasi") { return pass(quasiType, afterType); }
|
||||||
|
}
|
||||||
|
function maybeReturnType(type) {
|
||||||
|
if (type == "=>") return cont(typeexpr)
|
||||||
|
}
|
||||||
|
function typeprops(type) {
|
||||||
|
if (type.match(/[\}\)\]]/)) return cont()
|
||||||
|
if (type == "," || type == ";") return cont(typeprops)
|
||||||
|
return pass(typeprop, typeprops)
|
||||||
|
}
|
||||||
|
function typeprop(type, value) {
|
||||||
|
if (type == "variable" || cx.style == "keyword") {
|
||||||
|
cx.marked = "property"
|
||||||
|
return cont(typeprop)
|
||||||
|
} else if (value == "?" || type == "number" || type == "string") {
|
||||||
|
return cont(typeprop)
|
||||||
|
} else if (type == ":") {
|
||||||
|
return cont(typeexpr)
|
||||||
|
} else if (type == "[") {
|
||||||
|
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
|
||||||
|
} else if (type == "(") {
|
||||||
|
return pass(functiondecl, typeprop)
|
||||||
|
} else if (!type.match(/[;\}\)\],]/)) {
|
||||||
|
return cont()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function quasiType(type, value) {
|
||||||
|
if (type != "quasi") return pass();
|
||||||
|
if (value.slice(value.length - 2) != "${") return cont(quasiType);
|
||||||
|
return cont(typeexpr, continueQuasiType);
|
||||||
|
}
|
||||||
|
function continueQuasiType(type) {
|
||||||
|
if (type == "}") {
|
||||||
|
cx.marked = "string-2";
|
||||||
|
cx.state.tokenize = tokenQuasi;
|
||||||
|
return cont(quasiType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function typearg(type, value) {
|
||||||
|
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
|
||||||
|
if (type == ":") return cont(typeexpr)
|
||||||
|
if (type == "spread") return cont(typearg)
|
||||||
|
return pass(typeexpr)
|
||||||
|
}
|
||||||
|
function afterType(type, value) {
|
||||||
|
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
||||||
|
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
|
||||||
|
if (type == "[") return cont(typeexpr, expect("]"), afterType)
|
||||||
|
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
|
||||||
|
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
|
||||||
|
}
|
||||||
|
function maybeTypeArgs(_, value) {
|
||||||
|
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
||||||
|
}
|
||||||
|
function typeparam() {
|
||||||
|
return pass(typeexpr, maybeTypeDefault)
|
||||||
|
}
|
||||||
|
function maybeTypeDefault(_, value) {
|
||||||
|
if (value == "=") return cont(typeexpr)
|
||||||
|
}
|
||||||
|
function vardef(_, value) {
|
||||||
|
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
|
||||||
|
return pass(pattern, maybetype, maybeAssign, vardefCont);
|
||||||
|
}
|
||||||
|
function pattern(type, value) {
|
||||||
|
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
|
||||||
|
if (type == "variable") { register(value); return cont(); }
|
||||||
|
if (type == "spread") return cont(pattern);
|
||||||
|
if (type == "[") return contCommasep(eltpattern, "]");
|
||||||
|
if (type == "{") return contCommasep(proppattern, "}");
|
||||||
|
}
|
||||||
|
function proppattern(type, value) {
|
||||||
|
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
|
||||||
|
register(value);
|
||||||
|
return cont(maybeAssign);
|
||||||
|
}
|
||||||
|
if (type == "variable") cx.marked = "property";
|
||||||
|
if (type == "spread") return cont(pattern);
|
||||||
|
if (type == "}") return pass();
|
||||||
|
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
|
||||||
|
return cont(expect(":"), pattern, maybeAssign);
|
||||||
|
}
|
||||||
|
function eltpattern() {
|
||||||
|
return pass(pattern, maybeAssign)
|
||||||
|
}
|
||||||
|
function maybeAssign(_type, value) {
|
||||||
|
if (value == "=") return cont(expressionNoComma);
|
||||||
|
}
|
||||||
|
function vardefCont(type) {
|
||||||
|
if (type == ",") return cont(vardef);
|
||||||
|
}
|
||||||
|
function maybeelse(type, value) {
|
||||||
|
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
|
||||||
|
}
|
||||||
|
function forspec(type, value) {
|
||||||
|
if (value == "await") return cont(forspec);
|
||||||
|
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
|
||||||
|
}
|
||||||
|
function forspec1(type) {
|
||||||
|
if (type == "var") return cont(vardef, forspec2);
|
||||||
|
if (type == "variable") return cont(forspec2);
|
||||||
|
return pass(forspec2)
|
||||||
|
}
|
||||||
|
function forspec2(type, value) {
|
||||||
|
if (type == ")") return cont()
|
||||||
|
if (type == ";") return cont(forspec2)
|
||||||
|
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
|
||||||
|
return pass(expression, forspec2)
|
||||||
|
}
|
||||||
|
function functiondef(type, value) {
|
||||||
|
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
|
||||||
|
if (type == "variable") {register(value); return cont(functiondef);}
|
||||||
|
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
|
||||||
|
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
|
||||||
|
}
|
||||||
|
function functiondecl(type, value) {
|
||||||
|
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
|
||||||
|
if (type == "variable") {register(value); return cont(functiondecl);}
|
||||||
|
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
|
||||||
|
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
|
||||||
|
}
|
||||||
|
function typename(type, value) {
|
||||||
|
if (type == "keyword" || type == "variable") {
|
||||||
|
cx.marked = "type"
|
||||||
|
return cont(typename)
|
||||||
|
} else if (value == "<") {
|
||||||
|
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function funarg(type, value) {
|
||||||
|
if (value == "@") cont(expression, funarg)
|
||||||
|
if (type == "spread") return cont(funarg);
|
||||||
|
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
|
||||||
|
if (isTS && type == "this") return cont(maybetype, maybeAssign)
|
||||||
|
return pass(pattern, maybetype, maybeAssign);
|
||||||
|
}
|
||||||
|
function classExpression(type, value) {
|
||||||
|
// Class expressions may have an optional name.
|
||||||
|
if (type == "variable") return className(type, value);
|
||||||
|
return classNameAfter(type, value);
|
||||||
|
}
|
||||||
|
function className(type, value) {
|
||||||
|
if (type == "variable") {register(value); return cont(classNameAfter);}
|
||||||
|
}
|
||||||
|
function classNameAfter(type, value) {
|
||||||
|
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
|
||||||
|
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
|
||||||
|
if (value == "implements") cx.marked = "keyword";
|
||||||
|
return cont(isTS ? typeexpr : expression, classNameAfter);
|
||||||
|
}
|
||||||
|
if (type == "{") return cont(pushlex("}"), classBody, poplex);
|
||||||
|
}
|
||||||
|
function classBody(type, value) {
|
||||||
|
if (type == "async" ||
|
||||||
|
(type == "variable" &&
|
||||||
|
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
|
||||||
|
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
|
||||||
|
cx.marked = "keyword";
|
||||||
|
return cont(classBody);
|
||||||
|
}
|
||||||
|
if (type == "variable" || cx.style == "keyword") {
|
||||||
|
cx.marked = "property";
|
||||||
|
return cont(classfield, classBody);
|
||||||
|
}
|
||||||
|
if (type == "number" || type == "string") return cont(classfield, classBody);
|
||||||
|
if (type == "[")
|
||||||
|
return cont(expression, maybetype, expect("]"), classfield, classBody)
|
||||||
|
if (value == "*") {
|
||||||
|
cx.marked = "keyword";
|
||||||
|
return cont(classBody);
|
||||||
|
}
|
||||||
|
if (isTS && type == "(") return pass(functiondecl, classBody)
|
||||||
|
if (type == ";" || type == ",") return cont(classBody);
|
||||||
|
if (type == "}") return cont();
|
||||||
|
if (value == "@") return cont(expression, classBody)
|
||||||
|
}
|
||||||
|
function classfield(type, value) {
|
||||||
|
if (value == "!") return cont(classfield)
|
||||||
|
if (value == "?") return cont(classfield)
|
||||||
|
if (type == ":") return cont(typeexpr, maybeAssign)
|
||||||
|
if (value == "=") return cont(expressionNoComma)
|
||||||
|
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
|
||||||
|
return pass(isInterface ? functiondecl : functiondef)
|
||||||
|
}
|
||||||
|
function afterExport(type, value) {
|
||||||
|
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
|
||||||
|
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
|
||||||
|
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
|
||||||
|
return pass(statement);
|
||||||
|
}
|
||||||
|
function exportField(type, value) {
|
||||||
|
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
|
||||||
|
if (type == "variable") return pass(expressionNoComma, exportField);
|
||||||
|
}
|
||||||
|
function afterImport(type) {
|
||||||
|
if (type == "string") return cont();
|
||||||
|
if (type == "(") return pass(expression);
|
||||||
|
if (type == ".") return pass(maybeoperatorComma);
|
||||||
|
return pass(importSpec, maybeMoreImports, maybeFrom);
|
||||||
|
}
|
||||||
|
function importSpec(type, value) {
|
||||||
|
if (type == "{") return contCommasep(importSpec, "}");
|
||||||
|
if (type == "variable") register(value);
|
||||||
|
if (value == "*") cx.marked = "keyword";
|
||||||
|
return cont(maybeAs);
|
||||||
|
}
|
||||||
|
function maybeMoreImports(type) {
|
||||||
|
if (type == ",") return cont(importSpec, maybeMoreImports)
|
||||||
|
}
|
||||||
|
function maybeAs(_type, value) {
|
||||||
|
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
|
||||||
|
}
|
||||||
|
function maybeFrom(_type, value) {
|
||||||
|
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
|
||||||
|
}
|
||||||
|
function arrayLiteral(type) {
|
||||||
|
if (type == "]") return cont();
|
||||||
|
return pass(commasep(expressionNoComma, "]"));
|
||||||
|
}
|
||||||
|
function enumdef() {
|
||||||
|
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
|
||||||
|
}
|
||||||
|
function enummember() {
|
||||||
|
return pass(pattern, maybeAssign);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isContinuedStatement(state, textAfter) {
|
||||||
|
return state.lastType == "operator" || state.lastType == "," ||
|
||||||
|
isOperatorChar.test(textAfter.charAt(0)) ||
|
||||||
|
/[,.]/.test(textAfter.charAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
function expressionAllowed(stream, state, backUp) {
|
||||||
|
return state.tokenize == tokenBase &&
|
||||||
|
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
|
||||||
|
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface
|
||||||
|
|
||||||
|
return {
|
||||||
|
startState: function(basecolumn) {
|
||||||
|
var state = {
|
||||||
|
tokenize: tokenBase,
|
||||||
|
lastType: "sof",
|
||||||
|
cc: [],
|
||||||
|
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
|
||||||
|
localVars: parserConfig.localVars,
|
||||||
|
context: parserConfig.localVars && new Context(null, null, false),
|
||||||
|
indented: basecolumn || 0
|
||||||
|
};
|
||||||
|
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
|
||||||
|
state.globalVars = parserConfig.globalVars;
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
|
||||||
|
token: function(stream, state) {
|
||||||
|
if (stream.sol()) {
|
||||||
|
if (!state.lexical.hasOwnProperty("align"))
|
||||||
|
state.lexical.align = false;
|
||||||
|
state.indented = stream.indentation();
|
||||||
|
findFatArrow(stream, state);
|
||||||
|
}
|
||||||
|
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
|
||||||
|
var style = state.tokenize(stream, state);
|
||||||
|
if (type == "comment") return style;
|
||||||
|
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
|
||||||
|
return parseJS(state, style, type, content, stream);
|
||||||
|
},
|
||||||
|
|
||||||
|
indent: function(state, textAfter) {
|
||||||
|
if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass;
|
||||||
|
if (state.tokenize != tokenBase) return 0;
|
||||||
|
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
|
||||||
|
// Kludge to prevent 'maybelse' from blocking lexical scope pops
|
||||||
|
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
|
||||||
|
var c = state.cc[i];
|
||||||
|
if (c == poplex) lexical = lexical.prev;
|
||||||
|
else if (c != maybeelse && c != popcontext) break;
|
||||||
|
}
|
||||||
|
while ((lexical.type == "stat" || lexical.type == "form") &&
|
||||||
|
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
|
||||||
|
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
|
||||||
|
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
|
||||||
|
lexical = lexical.prev;
|
||||||
|
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
|
||||||
|
lexical = lexical.prev;
|
||||||
|
var type = lexical.type, closing = firstChar == type;
|
||||||
|
|
||||||
|
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
|
||||||
|
else if (type == "form" && firstChar == "{") return lexical.indented;
|
||||||
|
else if (type == "form") return lexical.indented + indentUnit;
|
||||||
|
else if (type == "stat")
|
||||||
|
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
|
||||||
|
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
|
||||||
|
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
|
||||||
|
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
|
||||||
|
else return lexical.indented + (closing ? 0 : indentUnit);
|
||||||
|
},
|
||||||
|
|
||||||
|
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
|
||||||
|
blockCommentStart: jsonMode ? null : "/*",
|
||||||
|
blockCommentEnd: jsonMode ? null : "*/",
|
||||||
|
blockCommentContinue: jsonMode ? null : " * ",
|
||||||
|
lineComment: jsonMode ? null : "//",
|
||||||
|
fold: "brace",
|
||||||
|
closeBrackets: "()[]{}''\"\"``",
|
||||||
|
|
||||||
|
helperType: jsonMode ? "json" : "javascript",
|
||||||
|
jsonldMode: jsonldMode,
|
||||||
|
jsonMode: jsonMode,
|
||||||
|
|
||||||
|
expressionAllowed: expressionAllowed,
|
||||||
|
|
||||||
|
skipExpression: function(state) {
|
||||||
|
parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
|
||||||
|
|
||||||
|
CodeMirror.defineMIME("text/javascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("text/ecmascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("application/javascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("application/x-javascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("application/ecmascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("application/json", { name: "javascript", json: true });
|
||||||
|
CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true });
|
||||||
|
CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true })
|
||||||
|
CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true });
|
||||||
|
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
|
||||||
|
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
|
||||||
|
|
||||||
|
});
|
||||||
32076
web/assets/codemirror/jshint.js
Normal file
32076
web/assets/codemirror/jshint.js
Normal file
File diff suppressed because one or more lines are too long
1
web/assets/codemirror/jsonlint.js
Normal file
1
web/assets/codemirror/jsonlint.js
Normal file
File diff suppressed because one or more lines are too long
65
web/assets/codemirror/lint/javascript-lint.js
Normal file
65
web/assets/codemirror/lint/javascript-lint.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
// Depends on jshint.js from https://github.com/jshint/jshint
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
// declare global: JSHINT
|
||||||
|
|
||||||
|
function validator(text, options) {
|
||||||
|
if (!window.JSHINT) {
|
||||||
|
if (window.console) {
|
||||||
|
window.console.error("Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run.");
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!options.indent) // JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
|
||||||
|
options.indent = 1; // JSHint default value is 4
|
||||||
|
JSHINT(text, options, options.globals);
|
||||||
|
var errors = JSHINT.data().errors, result = [];
|
||||||
|
if (errors) parseErrors(errors, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("lint", "javascript", validator);
|
||||||
|
|
||||||
|
function parseErrors(errors, output) {
|
||||||
|
for ( var i = 0; i < errors.length; i++) {
|
||||||
|
var error = errors[i];
|
||||||
|
if (error) {
|
||||||
|
if (error.line <= 0) {
|
||||||
|
if (window.console) {
|
||||||
|
window.console.warn("Cannot display JSHint error (invalid line " + error.line + ")", error);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = error.character - 1, end = start + 1;
|
||||||
|
if (error.evidence) {
|
||||||
|
var index = error.evidence.substring(start).search(/.\b/);
|
||||||
|
if (index > -1) {
|
||||||
|
end += index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to format expected by validation service
|
||||||
|
var hint = {
|
||||||
|
message: error.reason,
|
||||||
|
severity: error.code ? (error.code.startsWith('W') ? "warning" : "error") : "error",
|
||||||
|
from: CodeMirror.Pos(error.line - 1, start),
|
||||||
|
to: CodeMirror.Pos(error.line - 1, end)
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push(hint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
79
web/assets/codemirror/lint/lint.css
Normal file
79
web/assets/codemirror/lint/lint.css
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/* The lint marker gutter */
|
||||||
|
.CodeMirror-lint-markers {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-tooltip {
|
||||||
|
background-color: #ffd;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 4px 4px 4px 4px;
|
||||||
|
color: black;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 10pt;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2px 5px;
|
||||||
|
position: fixed;
|
||||||
|
white-space: pre;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
z-index: 100;
|
||||||
|
max-width: 600px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .4s;
|
||||||
|
-moz-transition: opacity .4s;
|
||||||
|
-webkit-transition: opacity .4s;
|
||||||
|
-o-transition: opacity .4s;
|
||||||
|
-ms-transition: opacity .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark {
|
||||||
|
background-position: left bottom;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-warning {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-error {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker {
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-message {
|
||||||
|
padding-left: 18px;
|
||||||
|
background-position: top left;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-multiple {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right bottom;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-line-error {
|
||||||
|
background-color: rgba(183, 76, 81, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-line-warning {
|
||||||
|
background-color: rgba(255, 211, 0, 0.1);
|
||||||
|
}
|
||||||
288
web/assets/codemirror/lint/lint.js
Normal file
288
web/assets/codemirror/lint/lint.js
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
var GUTTER_ID = "CodeMirror-lint-markers";
|
||||||
|
var LINT_LINE_ID = "CodeMirror-lint-line-";
|
||||||
|
|
||||||
|
function showTooltip(cm, e, content) {
|
||||||
|
var tt = document.createElement("div");
|
||||||
|
tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme;
|
||||||
|
tt.appendChild(content.cloneNode(true));
|
||||||
|
if (cm.state.lint.options.selfContain)
|
||||||
|
cm.getWrapperElement().appendChild(tt);
|
||||||
|
else
|
||||||
|
document.body.appendChild(tt);
|
||||||
|
|
||||||
|
function position(e) {
|
||||||
|
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
|
||||||
|
var top = Math.max(0, e.clientY - tt.offsetHeight - 5);
|
||||||
|
var left = Math.max(0, Math.min(e.clientX + 5, tt.ownerDocument.defaultView.innerWidth - tt.offsetWidth));
|
||||||
|
tt.style.top = top + "px"
|
||||||
|
tt.style.left = left + "px";
|
||||||
|
}
|
||||||
|
CodeMirror.on(document, "mousemove", position);
|
||||||
|
position(e);
|
||||||
|
if (tt.style.opacity != null) tt.style.opacity = 1;
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
function rm(elt) {
|
||||||
|
if (elt.parentNode) elt.parentNode.removeChild(elt);
|
||||||
|
}
|
||||||
|
function hideTooltip(tt) {
|
||||||
|
if (!tt.parentNode) return;
|
||||||
|
if (tt.style.opacity == null) rm(tt);
|
||||||
|
tt.style.opacity = 0;
|
||||||
|
setTimeout(function() { rm(tt); }, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltipFor(cm, e, content, node) {
|
||||||
|
var tooltip = showTooltip(cm, e, content);
|
||||||
|
function hide() {
|
||||||
|
CodeMirror.off(node, "mouseout", hide);
|
||||||
|
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
|
||||||
|
}
|
||||||
|
var poll = setInterval(function() {
|
||||||
|
if (tooltip) for (var n = node;; n = n.parentNode) {
|
||||||
|
if (n && n.nodeType == 11) n = n.host;
|
||||||
|
if (n == document.body) return;
|
||||||
|
if (!n) { hide(); break; }
|
||||||
|
}
|
||||||
|
if (!tooltip) return clearInterval(poll);
|
||||||
|
}, 400);
|
||||||
|
CodeMirror.on(node, "mouseout", hide);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LintState(cm, conf, hasGutter) {
|
||||||
|
this.marked = [];
|
||||||
|
if (conf instanceof Function) conf = {getAnnotations: conf};
|
||||||
|
if (!conf || conf === true) conf = {};
|
||||||
|
this.options = {};
|
||||||
|
this.linterOptions = conf.options || {};
|
||||||
|
for (var prop in defaults) this.options[prop] = defaults[prop];
|
||||||
|
for (var prop in conf) {
|
||||||
|
if (defaults.hasOwnProperty(prop)) {
|
||||||
|
if (conf[prop] != null) this.options[prop] = conf[prop];
|
||||||
|
} else if (!conf.options) {
|
||||||
|
this.linterOptions[prop] = conf[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.timeout = null;
|
||||||
|
this.hasGutter = hasGutter;
|
||||||
|
this.onMouseOver = function(e) { onMouseOver(cm, e); };
|
||||||
|
this.waitingFor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = {
|
||||||
|
highlightLines: false,
|
||||||
|
tooltips: true,
|
||||||
|
delay: 500,
|
||||||
|
lintOnChange: true,
|
||||||
|
getAnnotations: null,
|
||||||
|
async: false,
|
||||||
|
selfContain: null,
|
||||||
|
formatAnnotation: null,
|
||||||
|
onUpdateLinting: null
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMarks(cm) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
|
||||||
|
if (state.options.highlightLines) clearErrorLines(cm);
|
||||||
|
for (var i = 0; i < state.marked.length; ++i)
|
||||||
|
state.marked[i].clear();
|
||||||
|
state.marked.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearErrorLines(cm) {
|
||||||
|
cm.eachLine(function(line) {
|
||||||
|
var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass);
|
||||||
|
if (has) cm.removeLineClass(line, "wrap", has[0]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMarker(cm, labels, severity, multiple, tooltips) {
|
||||||
|
var marker = document.createElement("div"), inner = marker;
|
||||||
|
marker.className = "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity;
|
||||||
|
if (multiple) {
|
||||||
|
inner = marker.appendChild(document.createElement("div"));
|
||||||
|
inner.className = "CodeMirror-lint-marker CodeMirror-lint-marker-multiple";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
|
||||||
|
showTooltipFor(cm, e, labels, inner);
|
||||||
|
});
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMaxSeverity(a, b) {
|
||||||
|
if (a == "error") return a;
|
||||||
|
else return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupByLine(annotations) {
|
||||||
|
var lines = [];
|
||||||
|
for (var i = 0; i < annotations.length; ++i) {
|
||||||
|
var ann = annotations[i], line = ann.from.line;
|
||||||
|
(lines[line] || (lines[line] = [])).push(ann);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function annotationTooltip(ann) {
|
||||||
|
var severity = ann.severity;
|
||||||
|
if (!severity) severity = "error";
|
||||||
|
var tip = document.createElement("div");
|
||||||
|
tip.className = "CodeMirror-lint-message CodeMirror-lint-message-" + severity;
|
||||||
|
if (typeof ann.messageHTML != 'undefined') {
|
||||||
|
tip.innerHTML = ann.messageHTML;
|
||||||
|
} else {
|
||||||
|
tip.appendChild(document.createTextNode(ann.message));
|
||||||
|
}
|
||||||
|
return tip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lintAsync(cm, getAnnotations) {
|
||||||
|
var state = cm.state.lint
|
||||||
|
var id = ++state.waitingFor
|
||||||
|
function abort() {
|
||||||
|
id = -1
|
||||||
|
cm.off("change", abort)
|
||||||
|
}
|
||||||
|
cm.on("change", abort)
|
||||||
|
getAnnotations(cm.getValue(), function(annotations, arg2) {
|
||||||
|
cm.off("change", abort)
|
||||||
|
if (state.waitingFor != id) return
|
||||||
|
if (arg2 && annotations instanceof CodeMirror) annotations = arg2
|
||||||
|
cm.operation(function() {updateLinting(cm, annotations)})
|
||||||
|
}, state.linterOptions, cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLinting(cm) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (!state) return;
|
||||||
|
var options = state.options;
|
||||||
|
/*
|
||||||
|
* Passing rules in `options` property prevents JSHint (and other linters) from complaining
|
||||||
|
* about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
|
||||||
|
*/
|
||||||
|
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
|
||||||
|
if (!getAnnotations) return;
|
||||||
|
if (options.async || getAnnotations.async) {
|
||||||
|
lintAsync(cm, getAnnotations)
|
||||||
|
} else {
|
||||||
|
var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm);
|
||||||
|
if (!annotations) return;
|
||||||
|
if (annotations.then) annotations.then(function(issues) {
|
||||||
|
cm.operation(function() {updateLinting(cm, issues)})
|
||||||
|
});
|
||||||
|
else cm.operation(function() {updateLinting(cm, annotations)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLinting(cm, annotationsNotSorted) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (!state) return;
|
||||||
|
var options = state.options;
|
||||||
|
clearMarks(cm);
|
||||||
|
|
||||||
|
var annotations = groupByLine(annotationsNotSorted);
|
||||||
|
|
||||||
|
for (var line = 0; line < annotations.length; ++line) {
|
||||||
|
var anns = annotations[line];
|
||||||
|
if (!anns) continue;
|
||||||
|
|
||||||
|
var maxSeverity = null;
|
||||||
|
var tipLabel = state.hasGutter && document.createDocumentFragment();
|
||||||
|
|
||||||
|
for (var i = 0; i < anns.length; ++i) {
|
||||||
|
var ann = anns[i];
|
||||||
|
var severity = ann.severity;
|
||||||
|
if (!severity) severity = "error";
|
||||||
|
maxSeverity = getMaxSeverity(maxSeverity, severity);
|
||||||
|
|
||||||
|
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
|
||||||
|
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
|
||||||
|
|
||||||
|
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
|
||||||
|
className: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity,
|
||||||
|
__annotation: ann
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (state.hasGutter)
|
||||||
|
cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1,
|
||||||
|
options.tooltips));
|
||||||
|
|
||||||
|
if (options.highlightLines)
|
||||||
|
cm.addLineClass(line, "wrap", LINT_LINE_ID + maxSeverity);
|
||||||
|
}
|
||||||
|
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(cm) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (!state) return;
|
||||||
|
clearTimeout(state.timeout);
|
||||||
|
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function popupTooltips(cm, annotations, e) {
|
||||||
|
var target = e.target || e.srcElement;
|
||||||
|
var tooltip = document.createDocumentFragment();
|
||||||
|
for (var i = 0; i < annotations.length; i++) {
|
||||||
|
var ann = annotations[i];
|
||||||
|
tooltip.appendChild(annotationTooltip(ann));
|
||||||
|
}
|
||||||
|
showTooltipFor(cm, e, tooltip, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseOver(cm, e) {
|
||||||
|
var target = e.target || e.srcElement;
|
||||||
|
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
||||||
|
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
||||||
|
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
||||||
|
|
||||||
|
var annotations = [];
|
||||||
|
for (var i = 0; i < spans.length; ++i) {
|
||||||
|
var ann = spans[i].__annotation;
|
||||||
|
if (ann) annotations.push(ann);
|
||||||
|
}
|
||||||
|
if (annotations.length) popupTooltips(cm, annotations, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
||||||
|
if (old && old != CodeMirror.Init) {
|
||||||
|
clearMarks(cm);
|
||||||
|
if (cm.state.lint.options.lintOnChange !== false)
|
||||||
|
cm.off("change", onChange);
|
||||||
|
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
|
||||||
|
clearTimeout(cm.state.lint.timeout);
|
||||||
|
delete cm.state.lint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
var gutters = cm.getOption("gutters"), hasLintGutter = false;
|
||||||
|
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
|
||||||
|
var state = cm.state.lint = new LintState(cm, val, hasLintGutter);
|
||||||
|
if (state.options.lintOnChange)
|
||||||
|
cm.on("change", onChange);
|
||||||
|
if (state.options.tooltips != false && state.options.tooltips != "gutter")
|
||||||
|
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
|
||||||
|
|
||||||
|
startLinting(cm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("performLint", function() {
|
||||||
|
startLinting(this);
|
||||||
|
});
|
||||||
|
});
|
||||||
86
web/assets/codemirror/xq.css
Normal file
86
web/assets/codemirror/xq.css
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
Copyright (C) 2011 by MarkLogic Corporation
|
||||||
|
Author: Mike Brevoort <mike@brevoort.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
.cm-s-xq.CodeMirror { border-radius: 1.5rem; border: 1px solid #d9d9d9; height: auto; }
|
||||||
|
.cm-s-xq.CodeMirror:hover { background-color: #edf4fa; border-color: #2f67c2; transition: all .3s; }
|
||||||
|
.cm-s-xq .CodeMirror-gutters { border-right: 1px solid #ddd; background-color: rgb(221 221 221 / 20%); white-space: nowrap; }
|
||||||
|
.cm-s-xq span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; }
|
||||||
|
.cm-s-xq span.cm-atom { color: #7A316F; font-weight:bold; }
|
||||||
|
.cm-s-xq span.cm-number { color: #389E0D; }
|
||||||
|
.cm-s-xq span.cm-def { text-decoration:underline; }
|
||||||
|
.cm-s-xq span.cm-variable { color: black; }
|
||||||
|
.cm-s-xq span.cm-variable-2 { color:black; }
|
||||||
|
.cm-s-xq span.cm-variable-3, .cm-s-xq span.cm-type { color: black; }
|
||||||
|
.cm-s-xq span.cm-property { color: #0e49b5; }
|
||||||
|
.cm-s-xq span.cm-operator {}
|
||||||
|
.cm-s-xq span.cm-comment { color: #bbbbbb; font-style: italic; }
|
||||||
|
.cm-s-xq span.cm-string {}
|
||||||
|
.cm-s-xq span.cm-meta { color: yellow; }
|
||||||
|
.cm-s-xq span.cm-qualifier { color: grey; }
|
||||||
|
.cm-s-xq span.cm-builtin { color: #7EA656; }
|
||||||
|
.cm-s-xq span.cm-bracket { color: #cc7; }
|
||||||
|
.cm-s-xq span.cm-tag { color: #3F7F7F; }
|
||||||
|
.cm-s-xq span.cm-attribute { color: #7F007F; }
|
||||||
|
.cm-s-xq span.cm-error { color: #e04141; }
|
||||||
|
|
||||||
|
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
|
||||||
|
.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:hover { background-color: #0e2040; border-color: #0e49b5; transition: all .3s; }
|
||||||
|
.dark .cm-s-xq div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); }
|
||||||
|
.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::-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-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid #2c3950; }
|
||||||
|
.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-linenumber { color: rgb(255 255 255 / 50%); }
|
||||||
|
.dark .cm-s-xq .CodeMirror-cursor { border-left: 1px solid white; }
|
||||||
|
|
||||||
|
.dark .cm-s-xq span.cm-keyword { color: #FFBD40; }
|
||||||
|
.dark .cm-s-xq span.cm-atom { color: #c099ff; }
|
||||||
|
.dark .cm-s-xq span.cm-number { color: #9ccfd8; }
|
||||||
|
.dark .cm-s-xq span.cm-def { color: #FFF; text-decoration:underline; }
|
||||||
|
.dark .cm-s-xq span.cm-variable { color: #FFF; }
|
||||||
|
.dark .cm-s-xq span.cm-variable-2 { color: #EEE; }
|
||||||
|
.dark .cm-s-xq span.cm-variable-3, .dark .cm-s-xq span.cm-type { color: #DDD; }
|
||||||
|
.dark .cm-s-xq span.cm-property { color: #f6c177 }
|
||||||
|
.dark .cm-s-xq span.cm-operator {}
|
||||||
|
.dark .cm-s-xq span.cm-comment { color: gray; }
|
||||||
|
.dark .cm-s-xq span.cm-string {}
|
||||||
|
.dark .cm-s-xq span.cm-meta { color: yellow; }
|
||||||
|
.dark .cm-s-xq span.cm-qualifier { color: #FFF700; }
|
||||||
|
.dark .cm-s-xq span.cm-builtin { color: #30a; }
|
||||||
|
.dark .cm-s-xq span.cm-bracket { color: #cc7; }
|
||||||
|
.dark .cm-s-xq span.cm-tag { color: #FFBD40; }
|
||||||
|
.dark .cm-s-xq span.cm-attribute { color: #FFF700; }
|
||||||
|
.dark .cm-s-xq span.cm-error { color: #e04141; }
|
||||||
|
|
||||||
|
.dark .cm-s-xq .CodeMirror-activeline-background { background: #27282E; }
|
||||||
|
.dark .cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
|
||||||
|
|
||||||
|
.Line-Hover{transition: all .2s;}
|
||||||
|
.Line-Hover:hover{ background-color: rgb(4 48 143 / 5%) !important; }
|
||||||
|
.dark .Line-Hover:hover{ background-color: rgb(0 0 0 / 20%) !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; }
|
||||||
|
.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; }
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,11 @@ const supportLangs = [
|
|||||||
value: 'ru-RU',
|
value: 'ru-RU',
|
||||||
icon: '🇷🇺',
|
icon: '🇷🇺',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Tiếng Việt',
|
||||||
|
value: 'vi-VN',
|
||||||
|
icon: '🇻🇳',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLang() {
|
function getLang() {
|
||||||
@@ -60,4 +65,4 @@ function isSupportLang(lang) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,3 @@
|
|||||||
class User {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.username = "";
|
|
||||||
this.password = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Msg {
|
|
||||||
|
|
||||||
constructor(success, msg, obj) {
|
|
||||||
this.success = false;
|
|
||||||
this.msg = "";
|
|
||||||
this.obj = null;
|
|
||||||
|
|
||||||
if (success != null) {
|
|
||||||
this.success = success;
|
|
||||||
}
|
|
||||||
if (msg != null) {
|
|
||||||
this.msg = msg;
|
|
||||||
}
|
|
||||||
if (obj != null) {
|
|
||||||
this.obj = obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DBInbound {
|
class DBInbound {
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
@@ -139,6 +112,19 @@ class DBInbound {
|
|||||||
return Inbound.fromJson(config);
|
return Inbound.fromJson(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMultiUser() {
|
||||||
|
switch (this.protocol) {
|
||||||
|
case Protocols.VMESS:
|
||||||
|
case Protocols.VLESS:
|
||||||
|
case Protocols.TROJAN:
|
||||||
|
return true;
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return this.toInbound().isSSMultiUser;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasLink() {
|
hasLink() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
@@ -150,59 +136,9 @@ class DBInbound {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(address=this.address, remark=this.remark, clientIndex=0) {
|
|
||||||
const inbound = this.toInbound();
|
|
||||||
return inbound.genLink(address, remark, clientIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
get genInboundLinks() {
|
genInboundLinks(remarkModel) {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genInboundLinks(this.address, this.remark);
|
return inbound.genInboundLinks(this.remark,remarkModel);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AllSetting {
|
|
||||||
|
|
||||||
constructor(data) {
|
|
||||||
this.webListen = "";
|
|
||||||
this.webDomain = "";
|
|
||||||
this.webPort = 54321;
|
|
||||||
this.webCertFile = "";
|
|
||||||
this.webKeyFile = "";
|
|
||||||
this.webBasePath = "/";
|
|
||||||
this.sessionMaxAge = "";
|
|
||||||
this.expireDiff = "";
|
|
||||||
this.trafficDiff = "";
|
|
||||||
this.tgBotEnable = false;
|
|
||||||
this.tgBotToken = "";
|
|
||||||
this.tgBotChatId = "";
|
|
||||||
this.tgRunTime = "@daily";
|
|
||||||
this.tgBotBackup = false;
|
|
||||||
this.tgBotLoginNotify = false;
|
|
||||||
this.tgCpu = "";
|
|
||||||
this.tgLang = "";
|
|
||||||
this.xrayTemplateConfig = "";
|
|
||||||
this.subEnable = false;
|
|
||||||
this.subListen = "";
|
|
||||||
this.subPort = "2096";
|
|
||||||
this.subPath = "/sub/";
|
|
||||||
this.subDomain = "";
|
|
||||||
this.subCertFile = "";
|
|
||||||
this.subKeyFile = "";
|
|
||||||
this.subUpdates = 0;
|
|
||||||
this.subEncrypt = true;
|
|
||||||
this.subShowInfo = false;
|
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
|
||||||
|
|
||||||
if (data == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ObjectUtil.cloneProps(this, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(other) {
|
|
||||||
return ObjectUtil.equals(this, other);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
900
web/assets/js/model/outbound.js
Normal file
900
web/assets/js/model/outbound.js
Normal file
@@ -0,0 +1,900 @@
|
|||||||
|
const Protocols = {
|
||||||
|
Freedom: "freedom",
|
||||||
|
Blackhole: "blackhole",
|
||||||
|
DNS: "dns",
|
||||||
|
VMess: "vmess",
|
||||||
|
VLESS: "vless",
|
||||||
|
Trojan: "trojan",
|
||||||
|
Shadowsocks: "shadowsocks",
|
||||||
|
Socks: "socks",
|
||||||
|
HTTP: "http",
|
||||||
|
};
|
||||||
|
|
||||||
|
const SSMethods = {
|
||||||
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
|
AES_128_GCM: 'aes-128-gcm',
|
||||||
|
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
|
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||||
|
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||||
|
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||||
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
|
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||||
|
};
|
||||||
|
|
||||||
|
const TLS_FLOW_CONTROL = {
|
||||||
|
VISION: "xtls-rprx-vision",
|
||||||
|
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||||
|
};
|
||||||
|
|
||||||
|
const UTLS_FINGERPRINT = {
|
||||||
|
UTLS_CHROME: "chrome",
|
||||||
|
UTLS_FIREFOX: "firefox",
|
||||||
|
UTLS_SAFARI: "safari",
|
||||||
|
UTLS_IOS: "ios",
|
||||||
|
UTLS_android: "android",
|
||||||
|
UTLS_EDGE: "edge",
|
||||||
|
UTLS_360: "360",
|
||||||
|
UTLS_QQ: "qq",
|
||||||
|
UTLS_RANDOM: "random",
|
||||||
|
UTLS_RANDOMIZED: "randomized",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALPN_OPTION = {
|
||||||
|
H3: "h3",
|
||||||
|
H2: "h2",
|
||||||
|
HTTP1: "http/1.1",
|
||||||
|
};
|
||||||
|
|
||||||
|
const outboundDomainStrategies = [
|
||||||
|
"AsIs",
|
||||||
|
"UseIP",
|
||||||
|
"UseIPv4",
|
||||||
|
"UseIPv6"
|
||||||
|
]
|
||||||
|
|
||||||
|
Object.freeze(Protocols);
|
||||||
|
Object.freeze(SSMethods);
|
||||||
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
|
Object.freeze(ALPN_OPTION);
|
||||||
|
Object.freeze(outboundDomainStrategies);
|
||||||
|
|
||||||
|
class CommonClass {
|
||||||
|
|
||||||
|
static toJsonArray(arr) {
|
||||||
|
return arr.map(obj => obj.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson() {
|
||||||
|
return new CommonClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(format=true) {
|
||||||
|
return format ? JSON.stringify(this.toJson(), null, 2) : JSON.stringify(this.toJson());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TcpStreamSettings extends CommonClass {
|
||||||
|
constructor(type='none', host, path) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
this.host = host;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
let header = json.header;
|
||||||
|
if (!header) return new TcpStreamSettings();
|
||||||
|
if(header.type == 'http' && header.request){
|
||||||
|
return new TcpStreamSettings(
|
||||||
|
header.type,
|
||||||
|
header.request.headers.Host.join(','),
|
||||||
|
header.request.path.join(','),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new TcpStreamSettings(header.type,'','');
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
type: this.type,
|
||||||
|
request: this.type === 'http' ? {
|
||||||
|
headers: {
|
||||||
|
Host: ObjectUtil.isEmpty(this.host) ? [] : this.host.split(',')
|
||||||
|
},
|
||||||
|
path: ObjectUtil.isEmpty(this.path) ? ["/"] : this.path.split(',')
|
||||||
|
} : undefined,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KcpStreamSettings extends CommonClass {
|
||||||
|
constructor(mtu=1350, tti=20,
|
||||||
|
uplinkCapacity=5,
|
||||||
|
downlinkCapacity=20,
|
||||||
|
congestion=false,
|
||||||
|
readBufferSize=2,
|
||||||
|
writeBufferSize=2,
|
||||||
|
type='none',
|
||||||
|
seed='',
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.mtu = mtu;
|
||||||
|
this.tti = tti;
|
||||||
|
this.upCap = uplinkCapacity;
|
||||||
|
this.downCap = downlinkCapacity;
|
||||||
|
this.congestion = congestion;
|
||||||
|
this.readBuffer = readBufferSize;
|
||||||
|
this.writeBuffer = writeBufferSize;
|
||||||
|
this.type = type;
|
||||||
|
this.seed = seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new KcpStreamSettings(
|
||||||
|
json.mtu,
|
||||||
|
json.tti,
|
||||||
|
json.uplinkCapacity,
|
||||||
|
json.downlinkCapacity,
|
||||||
|
json.congestion,
|
||||||
|
json.readBufferSize,
|
||||||
|
json.writeBufferSize,
|
||||||
|
ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
|
||||||
|
json.seed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
mtu: this.mtu,
|
||||||
|
tti: this.tti,
|
||||||
|
uplinkCapacity: this.upCap,
|
||||||
|
downlinkCapacity: this.downCap,
|
||||||
|
congestion: this.congestion,
|
||||||
|
readBufferSize: this.readBuffer,
|
||||||
|
writeBufferSize: this.writeBuffer,
|
||||||
|
header: {
|
||||||
|
type: this.type,
|
||||||
|
},
|
||||||
|
seed: this.seed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WsStreamSettings extends CommonClass {
|
||||||
|
constructor(path='/', host='') {
|
||||||
|
super();
|
||||||
|
this.path = path;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new WsStreamSettings(
|
||||||
|
json.path,
|
||||||
|
json.headers && !ObjectUtil.isEmpty(json.headers.Host) ? json.headers.Host : '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
path: this.path,
|
||||||
|
headers: ObjectUtil.isEmpty(this.host) ? undefined : {Host: this.host},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpStreamSettings extends CommonClass {
|
||||||
|
constructor(path='/', host='') {
|
||||||
|
super();
|
||||||
|
this.path = path;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new HttpStreamSettings(
|
||||||
|
json.path,
|
||||||
|
json.host ? json.host.join(',') : '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
path: this.path,
|
||||||
|
host: ObjectUtil.isEmpty(this.host) ? [''] : this.host.split(','),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuicStreamSettings extends CommonClass {
|
||||||
|
constructor(security='none',
|
||||||
|
key='', type='none') {
|
||||||
|
super();
|
||||||
|
this.security = security;
|
||||||
|
this.key = key;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new QuicStreamSettings(
|
||||||
|
json.security,
|
||||||
|
json.key,
|
||||||
|
json.header ? json.header.type : 'none',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
security: this.security,
|
||||||
|
key: this.key,
|
||||||
|
header: {
|
||||||
|
type: this.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GrpcStreamSettings extends CommonClass {
|
||||||
|
constructor(serviceName="", multiMode=false) {
|
||||||
|
super();
|
||||||
|
this.serviceName = serviceName;
|
||||||
|
this.multiMode = multiMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new GrpcStreamSettings(json.serviceName, json.multiMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
serviceName: this.serviceName,
|
||||||
|
multiMode: this.multiMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TlsStreamSettings extends CommonClass {
|
||||||
|
constructor(serverName='',
|
||||||
|
alpn=[],
|
||||||
|
fingerprint = '',
|
||||||
|
allowInsecure = false) {
|
||||||
|
super();
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.alpn = alpn;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
this.allowInsecure = allowInsecure;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new TlsStreamSettings(
|
||||||
|
json.serverName,
|
||||||
|
json.alpn,
|
||||||
|
json.fingerprint,
|
||||||
|
json.allowInsecure,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
serverName: this.serverName,
|
||||||
|
alpn: this.alpn,
|
||||||
|
fingerprint: this.fingerprint,
|
||||||
|
allowInsecure: this.allowInsecure,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RealityStreamSettings extends CommonClass {
|
||||||
|
constructor(publicKey = '', fingerprint = '', serverName = '', shortId = '', spiderX = '/') {
|
||||||
|
super();
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.shortId = shortId
|
||||||
|
this.spiderX = spiderX;
|
||||||
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new RealityStreamSettings(
|
||||||
|
json.publicKey,
|
||||||
|
json.fingerprint,
|
||||||
|
json.serverName,
|
||||||
|
json.shortId,
|
||||||
|
json.spiderX,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
publicKey: this.publicKey,
|
||||||
|
fingerprint: this.fingerprint,
|
||||||
|
serverName: this.serverName,
|
||||||
|
shortId: this.shortId,
|
||||||
|
spiderX: this.spiderX,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class StreamSettings extends CommonClass {
|
||||||
|
constructor(network='tcp',
|
||||||
|
security='none',
|
||||||
|
tlsSettings=new TlsStreamSettings(),
|
||||||
|
realitySettings = new RealityStreamSettings(),
|
||||||
|
tcpSettings=new TcpStreamSettings(),
|
||||||
|
kcpSettings=new KcpStreamSettings(),
|
||||||
|
wsSettings=new WsStreamSettings(),
|
||||||
|
httpSettings=new HttpStreamSettings(),
|
||||||
|
quicSettings=new QuicStreamSettings(),
|
||||||
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.network = network;
|
||||||
|
this.security = security;
|
||||||
|
this.tls = tlsSettings;
|
||||||
|
this.reality = realitySettings;
|
||||||
|
this.tcp = tcpSettings;
|
||||||
|
this.kcp = kcpSettings;
|
||||||
|
this.ws = wsSettings;
|
||||||
|
this.http = httpSettings;
|
||||||
|
this.quic = quicSettings;
|
||||||
|
this.grpc = grpcSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTls() {
|
||||||
|
return this.security === 'tls';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReality() {
|
||||||
|
return this.security === "reality";
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new StreamSettings(
|
||||||
|
json.network,
|
||||||
|
json.security,
|
||||||
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
|
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||||
|
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||||
|
WsStreamSettings.fromJson(json.wsSettings),
|
||||||
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
const network = this.network;
|
||||||
|
return {
|
||||||
|
network: network,
|
||||||
|
security: this.security,
|
||||||
|
tlsSettings: this.security == 'tls' ? this.tls.toJson() : undefined,
|
||||||
|
realitySettings: this.security == 'reality' ? this.reality.toJson() : undefined,
|
||||||
|
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||||
|
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||||
|
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||||
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Outbound extends CommonClass {
|
||||||
|
constructor(
|
||||||
|
tag='',
|
||||||
|
protocol=Protocols.VMess,
|
||||||
|
settings=null,
|
||||||
|
streamSettings = new StreamSettings(),
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.tag = tag;
|
||||||
|
this._protocol = protocol;
|
||||||
|
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
|
||||||
|
this.stream = streamSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
get protocol() {
|
||||||
|
return this._protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
set protocol(protocol) {
|
||||||
|
this._protocol = protocol;
|
||||||
|
this.settings = Outbound.Settings.getSettings(protocol);
|
||||||
|
this.stream = new StreamSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
canEnableTls() {
|
||||||
|
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||||
|
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this is used for xtls-rprx-vision
|
||||||
|
canEnableTlsFlow() {
|
||||||
|
if ((this.stream.security != 'none') && (this.stream.network === "tcp")) {
|
||||||
|
return this.protocol === Protocols.VLESS;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
canEnableReality() {
|
||||||
|
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||||
|
return ["tcp", "http", "grpc"].includes(this.stream.network);
|
||||||
|
}
|
||||||
|
|
||||||
|
canEnableStream() {
|
||||||
|
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasVnext() {
|
||||||
|
return [Protocols.VMess, Protocols.VLESS].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasServers() {
|
||||||
|
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAddressPort() {
|
||||||
|
return [
|
||||||
|
Protocols.DNS,
|
||||||
|
Protocols.VMess,
|
||||||
|
Protocols.VLESS,
|
||||||
|
Protocols.Trojan,
|
||||||
|
Protocols.Shadowsocks,
|
||||||
|
Protocols.Socks,
|
||||||
|
Protocols.HTTP
|
||||||
|
].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasUsername() {
|
||||||
|
return [Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Outbound(
|
||||||
|
json.tag,
|
||||||
|
json.protocol,
|
||||||
|
Outbound.Settings.fromJson(json.protocol, json.settings),
|
||||||
|
StreamSettings.fromJson(json.streamSettings),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
tag: this.tag == '' ? undefined : this.tag,
|
||||||
|
protocol: this.protocol,
|
||||||
|
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
|
||||||
|
streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromLink(link) {
|
||||||
|
data = link.split('://');
|
||||||
|
if(data.length !=2) return null;
|
||||||
|
switch(data[0].toLowerCase()){
|
||||||
|
case Protocols.VMess:
|
||||||
|
return this.fromVmessLink(JSON.parse(atob(data[1])));
|
||||||
|
case Protocols.VLESS:
|
||||||
|
case Protocols.Trojan:
|
||||||
|
case 'ss':
|
||||||
|
return this.fromParamLink(link);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromVmessLink(json={}){
|
||||||
|
let stream = new StreamSettings(json.net, json.tls);
|
||||||
|
|
||||||
|
let network = json.net;
|
||||||
|
if (network === 'tcp') {
|
||||||
|
stream.tcp = new TcpStreamSettings(
|
||||||
|
json.type,
|
||||||
|
json.host ? json.host.split(','): [],
|
||||||
|
json.path ? json.path.split(','): []);
|
||||||
|
} else if (network === 'kcp') {
|
||||||
|
stream.kcp = new KcpStreamSettings();
|
||||||
|
stream.type = json.type;
|
||||||
|
stream.seed = json.path;
|
||||||
|
} else if (network === 'ws') {
|
||||||
|
stream.ws = new WsStreamSettings(json.path,json.host);
|
||||||
|
} else if (network === 'http' || network == 'h2') {
|
||||||
|
stream.network = 'http'
|
||||||
|
stream.http = new HttpStreamSettings(
|
||||||
|
json.path,
|
||||||
|
json.host ? json.host.split(',') : []);
|
||||||
|
} else if (network === 'quic') {
|
||||||
|
stream.quic = new QuicStreamSettings(
|
||||||
|
json.host ? json.host : 'none',
|
||||||
|
json.path,
|
||||||
|
json.type ? json.type : 'none');
|
||||||
|
} else if (network === 'grpc') {
|
||||||
|
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(json.tls && json.tls == 'tls'){
|
||||||
|
stream.tls = new TlsStreamSettings(
|
||||||
|
json.sni,
|
||||||
|
json.alpn ? json.alpn.split(',') : [],
|
||||||
|
json.fp,
|
||||||
|
json.allowInsecure);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromParamLink(link){
|
||||||
|
const url = new URL(link);
|
||||||
|
let type = url.searchParams.get('type');
|
||||||
|
let security = url.searchParams.get('security') ?? 'none';
|
||||||
|
let stream = new StreamSettings(type, security);
|
||||||
|
|
||||||
|
let headerType = url.searchParams.get('headerType');
|
||||||
|
let host = url.searchParams.get('host');
|
||||||
|
let path = url.searchParams.get('path');
|
||||||
|
|
||||||
|
if (type === 'tcp') {
|
||||||
|
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
|
||||||
|
} else if (type === 'kcp') {
|
||||||
|
stream.kcp = new KcpStreamSettings();
|
||||||
|
stream.kcp.type = headerType ?? 'none';
|
||||||
|
stream.kcp.seed = path;
|
||||||
|
} else if (type === 'ws') {
|
||||||
|
stream.ws = new WsStreamSettings(path,host);
|
||||||
|
} else if (type === 'http' || type == 'h2') {
|
||||||
|
stream.http = new HttpStreamSettings(path,host);
|
||||||
|
} else if (type === 'quic') {
|
||||||
|
stream.quic = new QuicStreamSettings(
|
||||||
|
url.searchParams.get('quicSecurity') ?? 'none',
|
||||||
|
url.searchParams.get('key') ?? '',
|
||||||
|
headerType ?? 'none');
|
||||||
|
} else if (type === 'grpc') {
|
||||||
|
stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(security == 'tls'){
|
||||||
|
let fp=url.searchParams.get('fp') ?? 'none';
|
||||||
|
let alpn=url.searchParams.get('alpn');
|
||||||
|
let allowInsecure=url.searchParams.get('allowInsecure');
|
||||||
|
let sni=url.searchParams.get('sni') ?? '';
|
||||||
|
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(security == 'reality'){
|
||||||
|
let pbk=url.searchParams.get('pbk');
|
||||||
|
let fp=url.searchParams.get('fp');
|
||||||
|
let sni=url.searchParams.get('sni') ?? '';
|
||||||
|
let sid=url.searchParams.get('sid') ?? '';
|
||||||
|
let spx=url.searchParams.get('spx') ?? '';
|
||||||
|
stream.tls = new RealityStreamSettings(pbk, fp, sni, sid, spx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = link.split('?');
|
||||||
|
if(data.length != 2) return null;
|
||||||
|
|
||||||
|
const regex = /([^@]+):\/\/([^@]+)@([^:]+):(\d+)\?(.*)$/;
|
||||||
|
const match = link.match(regex);
|
||||||
|
|
||||||
|
if (!match) return null;
|
||||||
|
let [, protocol, userData, address, port, ] = match;
|
||||||
|
port *= 1;
|
||||||
|
if(protocol == 'ss') {
|
||||||
|
protocol = 'shadowsocks';
|
||||||
|
userData = atob(userData).split(':');
|
||||||
|
}
|
||||||
|
var settings;
|
||||||
|
switch(protocol){
|
||||||
|
case Protocols.VLESS:
|
||||||
|
settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '');
|
||||||
|
break;
|
||||||
|
case Protocols.Trojan:
|
||||||
|
settings = new Outbound.TrojanSettings(address, port, userData);
|
||||||
|
break;
|
||||||
|
case Protocols.Shadowsocks:
|
||||||
|
let method = userData.splice(0,1)[0];
|
||||||
|
settings = new Outbound.ShadowsocksSettings(address, port, userData.join(":"), method, true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let remark = decodeURIComponent(url.hash);
|
||||||
|
// Remove '#' from url.hash
|
||||||
|
remark = remark.length > 0 ? remark.substring(1) : 'out-' + protocol + '-' + port;
|
||||||
|
return new Outbound(remark, protocol, settings, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Outbound.Settings = class extends CommonClass {
|
||||||
|
constructor(protocol) {
|
||||||
|
super();
|
||||||
|
this.protocol = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSettings(protocol) {
|
||||||
|
switch (protocol) {
|
||||||
|
case Protocols.Freedom: return new Outbound.FreedomSettings();
|
||||||
|
case Protocols.Blackhole: return new Outbound.BlackholeSettings();
|
||||||
|
case Protocols.DNS: return new Outbound.DNSSettings();
|
||||||
|
case Protocols.VMess: return new Outbound.VmessSettings();
|
||||||
|
case Protocols.VLESS: return new Outbound.VLESSSettings();
|
||||||
|
case Protocols.Trojan: return new Outbound.TrojanSettings();
|
||||||
|
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
|
||||||
|
case Protocols.Socks: return new Outbound.SocksSettings();
|
||||||
|
case Protocols.HTTP: return new Outbound.HttpSettings();
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(protocol, json) {
|
||||||
|
switch (protocol) {
|
||||||
|
case Protocols.Freedom: return Outbound.FreedomSettings.fromJson(json);
|
||||||
|
case Protocols.Blackhole: return Outbound.BlackholeSettings.fromJson(json);
|
||||||
|
case Protocols.DNS: return Outbound.DNSSettings.fromJson(json);
|
||||||
|
case Protocols.VMess: return Outbound.VmessSettings.fromJson(json);
|
||||||
|
case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json);
|
||||||
|
case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json);
|
||||||
|
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
|
||||||
|
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
|
||||||
|
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.FreedomSettings = class extends CommonClass {
|
||||||
|
constructor(domainStrategy='', fragment={}) {
|
||||||
|
super();
|
||||||
|
this.domainStrategy = domainStrategy;
|
||||||
|
this.fragment = fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Outbound.FreedomSettings(
|
||||||
|
json.domainStrategy,
|
||||||
|
json.fragment ? Outbound.FreedomSettings.Fragment.fromJson(json.fragment) : undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
domainStrategy: ObjectUtil.isEmpty(this.domainStrategy) ? undefined : this.domainStrategy,
|
||||||
|
fragment: Object.keys(this.fragment).length === 0 ? undefined : this.fragment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
||||||
|
constructor(packets='1-3',length='',interval=''){
|
||||||
|
super();
|
||||||
|
this.packets = packets;
|
||||||
|
this.length = length;
|
||||||
|
this.interval = interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Outbound.FreedomSettings.Fragment(
|
||||||
|
json.packets,
|
||||||
|
json.length,
|
||||||
|
json.interval,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.BlackholeSettings = class extends CommonClass {
|
||||||
|
constructor(type) {
|
||||||
|
super();
|
||||||
|
this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Outbound.BlackholeSettings(
|
||||||
|
json.response ? json.response.type : undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
response: ObjectUtil.isEmpty(this.type) ? undefined : {type: this.type},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.DNSSettings = class extends CommonClass {
|
||||||
|
constructor(network='udp', address='1.1.1.1', port=53) {
|
||||||
|
super();
|
||||||
|
this.network = network;
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}){
|
||||||
|
return new Outbound.DNSSettings(
|
||||||
|
json.network,
|
||||||
|
json.address,
|
||||||
|
json.port,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.VmessSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, id) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
if(ObjectUtil.isArrEmpty(json.vnext)) return new Outbound.VmessSettings();
|
||||||
|
return new Outbound.VmessSettings(
|
||||||
|
json.vnext[0].address,
|
||||||
|
json.vnext[0].port,
|
||||||
|
json.vnext[0].users[0].id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
vnext: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: [{id: this.id}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.VLESSSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, id, flow, encryption='none') {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.id = id;
|
||||||
|
this.flow = flow;
|
||||||
|
this.encryption = encryption
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
if(ObjectUtil.isArrEmpty(json.vnext)) return new Outbound.VLESSSettings();
|
||||||
|
return new Outbound.VLESSSettings(
|
||||||
|
json.vnext[0].address,
|
||||||
|
json.vnext[0].port,
|
||||||
|
json.vnext[0].users[0].id,
|
||||||
|
json.vnext[0].users[0].flow,
|
||||||
|
json.vnext[0].users[0].encryption,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
vnext: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: [{id: this.id, flow: this.flow, encryption: 'none',}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.TrojanSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, password) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
if(ObjectUtil.isArrEmpty(json.servers)) return new Outbound.TrojanSettings();
|
||||||
|
return new Outbound.TrojanSettings(
|
||||||
|
json.servers[0].address,
|
||||||
|
json.servers[0].port,
|
||||||
|
json.servers[0].password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
servers: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
password: this.password,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.ShadowsocksSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, password, method, uot) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.password = password;
|
||||||
|
this.method = method;
|
||||||
|
this.uot = uot;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
let servers = json.servers;
|
||||||
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{}];
|
||||||
|
return new Outbound.ShadowsocksSettings(
|
||||||
|
servers[0].address,
|
||||||
|
servers[0].port,
|
||||||
|
servers[0].password,
|
||||||
|
servers[0].method,
|
||||||
|
servers[0].uot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
servers: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
password: this.password,
|
||||||
|
method: this.method,
|
||||||
|
uot: this.uot,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.SocksSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, user, password) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.user = user;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
servers = json.servers;
|
||||||
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
|
return new Outbound.SocksSettings(
|
||||||
|
servers[0].address,
|
||||||
|
servers[0].port,
|
||||||
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
|
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
servers: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.HttpSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, user, password) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.user = user;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
servers = json.servers;
|
||||||
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
|
return new Outbound.HttpSettings(
|
||||||
|
servers[0].address,
|
||||||
|
servers[0].port,
|
||||||
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
|
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
servers: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
46
web/assets/js/model/setting.js
Normal file
46
web/assets/js/model/setting.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
class AllSetting {
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
this.webListen = "";
|
||||||
|
this.webDomain = "";
|
||||||
|
this.webPort = 54321;
|
||||||
|
this.webCertFile = "";
|
||||||
|
this.webKeyFile = "";
|
||||||
|
this.webBasePath = "/";
|
||||||
|
this.sessionMaxAge = "";
|
||||||
|
this.pageSize = 0;
|
||||||
|
this.expireDiff = "";
|
||||||
|
this.trafficDiff = "";
|
||||||
|
this.remarkModel = "-ieo";
|
||||||
|
this.tgBotEnable = false;
|
||||||
|
this.tgBotToken = "";
|
||||||
|
this.tgBotChatId = "";
|
||||||
|
this.tgRunTime = "@daily";
|
||||||
|
this.tgBotBackup = false;
|
||||||
|
this.tgBotLoginNotify = false;
|
||||||
|
this.tgCpu = "";
|
||||||
|
this.tgLang = "";
|
||||||
|
this.subEnable = false;
|
||||||
|
this.subListen = "";
|
||||||
|
this.subPort = "2096";
|
||||||
|
this.subPath = "/sub/";
|
||||||
|
this.subDomain = "";
|
||||||
|
this.subCertFile = "";
|
||||||
|
this.subKeyFile = "";
|
||||||
|
this.subUpdates = 0;
|
||||||
|
this.subEncrypt = true;
|
||||||
|
this.subShowInfo = false;
|
||||||
|
this.subURI = '';
|
||||||
|
|
||||||
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ObjectUtil.cloneProps(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(other) {
|
||||||
|
return ObjectUtil.equals(this, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,13 +8,6 @@ const Protocols = {
|
|||||||
HTTP: 'http',
|
HTTP: 'http',
|
||||||
};
|
};
|
||||||
|
|
||||||
const VmessMethods = {
|
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
|
||||||
AUTO: 'auto',
|
|
||||||
NONE: 'none',
|
|
||||||
};
|
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
AES_128_GCM: 'aes-128-gcm',
|
||||||
@@ -86,7 +79,6 @@ const SNIFFING_OPTION = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
Object.freeze(VmessMethods);
|
|
||||||
Object.freeze(SSMethods);
|
Object.freeze(SSMethods);
|
||||||
Object.freeze(TLS_FLOW_CONTROL);
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
Object.freeze(TLS_VERSION_OPTION);
|
Object.freeze(TLS_VERSION_OPTION);
|
||||||
@@ -413,7 +405,7 @@ class HttpStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class QuicStreamSettings extends XrayCommonClass {
|
class QuicStreamSettings extends XrayCommonClass {
|
||||||
constructor(security=VmessMethods.NONE,
|
constructor(security='none',
|
||||||
key='', type='none') {
|
key='', type='none') {
|
||||||
super();
|
super();
|
||||||
this.security = security;
|
this.security = security;
|
||||||
@@ -469,7 +461,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
alpn=[],
|
alpn=[],
|
||||||
settings=new TlsStreamSettings.Settings()) {
|
settings=new TlsStreamSettings.Settings()) {
|
||||||
super();
|
super();
|
||||||
this.server = serverName;
|
this.sni = serverName;
|
||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
this.maxVersion = maxVersion;
|
this.maxVersion = maxVersion;
|
||||||
this.cipherSuites = cipherSuites;
|
this.cipherSuites = cipherSuites;
|
||||||
@@ -511,7 +503,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serverName: this.server,
|
serverName: this.sni,
|
||||||
minVersion: this.minVersion,
|
minVersion: this.minVersion,
|
||||||
maxVersion: this.maxVersion,
|
maxVersion: this.maxVersion,
|
||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
@@ -570,27 +562,21 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(allowInsecure = false, fingerprint = '', serverName = '', domains = []) {
|
constructor(allowInsecure = false, fingerprint = '') {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
|
||||||
this.domains = domains;
|
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new TlsStreamSettings.Settings(
|
return new TlsStreamSettings.Settings(
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.serverName,
|
|
||||||
json.domains,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
|
||||||
domains: this.domains,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -708,6 +694,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
|||||||
class StreamSettings extends XrayCommonClass {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
|
externalProxy = [],
|
||||||
tlsSettings=new TlsStreamSettings(),
|
tlsSettings=new TlsStreamSettings(),
|
||||||
realitySettings = new RealityStreamSettings(),
|
realitySettings = new RealityStreamSettings(),
|
||||||
tcpSettings=new TcpStreamSettings(),
|
tcpSettings=new TcpStreamSettings(),
|
||||||
@@ -721,6 +708,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
super();
|
super();
|
||||||
this.network = network;
|
this.network = network;
|
||||||
this.security = security;
|
this.security = security;
|
||||||
|
this.externalProxy = externalProxy;
|
||||||
this.tls = tlsSettings;
|
this.tls = tlsSettings;
|
||||||
this.reality = realitySettings;
|
this.reality = realitySettings;
|
||||||
this.tcp = tcpSettings;
|
this.tcp = tcpSettings;
|
||||||
@@ -765,10 +753,10 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
|
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
json.network,
|
json.network,
|
||||||
json.security,
|
json.security,
|
||||||
|
json.externalProxy,
|
||||||
TlsStreamSettings.fromJson(json.tlsSettings),
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
RealityStreamSettings.fromJson(json.realitySettings),
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||||
@@ -786,6 +774,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
return {
|
return {
|
||||||
network: network,
|
network: network,
|
||||||
security: this.security,
|
security: this.security,
|
||||||
|
externalProxy: this.externalProxy,
|
||||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||||
@@ -844,6 +833,16 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.clientStats;
|
return this.clientStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get clients() {
|
||||||
|
switch (this.protocol) {
|
||||||
|
case Protocols.VMESS: return this.settings.vmesses;
|
||||||
|
case Protocols.VLESS: return this.settings.vlesses;
|
||||||
|
case Protocols.TROJAN: return this.settings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return this.isSSMultiUser ? this.settings.shadowsockses : null;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get protocol() {
|
get protocol() {
|
||||||
return this._protocol;
|
return this._protocol;
|
||||||
}
|
}
|
||||||
@@ -852,31 +851,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
this._protocol = protocol;
|
this._protocol = protocol;
|
||||||
this.settings = Inbound.Settings.getSettings(protocol);
|
this.settings = Inbound.Settings.getSettings(protocol);
|
||||||
if (protocol === Protocols.TROJAN) {
|
if (protocol === Protocols.TROJAN) {
|
||||||
this.tls = true;
|
this.stream.isTls = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get tls() {
|
|
||||||
return this.stream.security === 'tls';
|
|
||||||
}
|
|
||||||
|
|
||||||
set tls(isTls) {
|
|
||||||
if (isTls) {
|
|
||||||
this.stream.security = 'tls';
|
|
||||||
} else {
|
|
||||||
this.stream.security = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get reality() {
|
|
||||||
return this.stream.security === 'reality';
|
|
||||||
}
|
|
||||||
|
|
||||||
set reality(isReality) {
|
|
||||||
if (isReality) {
|
|
||||||
this.stream.security = 'reality';
|
|
||||||
} else {
|
|
||||||
this.stream.security = 'none';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,9 +904,8 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
if (this.stream.isTls || this.stream.isReality) {
|
if (this.stream.isTls) return this.stream.tls.sni;
|
||||||
return this.stream.tls.server;
|
if (this.stream.isReality) return this.stream.reality.serverNames;
|
||||||
}
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -982,108 +956,34 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isExpiry(index) {
|
isExpiry(index) {
|
||||||
switch (this.protocol) {
|
let exp = this.clients[index].expiryTime;
|
||||||
case Protocols.VMESS:
|
return exp > 0 ? exp < new Date().getTime() : false;
|
||||||
if(this.settings.vmesses[index].expiryTime > 0)
|
|
||||||
return this.settings.vmesses[index].expiryTime < new Date().getTime();
|
|
||||||
return false
|
|
||||||
case Protocols.VLESS:
|
|
||||||
if(this.settings.vlesses[index].expiryTime > 0)
|
|
||||||
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
|
||||||
return false
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
if(this.settings.trojans[index].expiryTime > 0)
|
|
||||||
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
|
||||||
return false
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
if(this.settings.shadowsockses.length > 0 && this.settings.shadowsockses[index].expiryTime > 0)
|
|
||||||
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
switch (this.protocol) {
|
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
|
||||||
case Protocols.VMESS:
|
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.network);
|
||||||
case Protocols.VLESS:
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.network) {
|
|
||||||
case "tcp":
|
|
||||||
case "ws":
|
|
||||||
case "http":
|
|
||||||
case "quic":
|
|
||||||
case "grpc":
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is used for xtls-rprx-vision
|
//this is used for xtls-rprx-vision
|
||||||
canEnableTlsFlow() {
|
canEnableTlsFlow() {
|
||||||
if ((this.stream.security != 'none') && (this.network === "tcp")) {
|
if ((this.stream.security != 'none') && (this.network === "tcp")) {
|
||||||
switch (this.protocol) {
|
return this.protocol === Protocols.VLESS;
|
||||||
case Protocols.VLESS:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
canSetTls() {
|
|
||||||
return this.canEnableTls();
|
|
||||||
}
|
|
||||||
|
|
||||||
canEnableReality() {
|
canEnableReality() {
|
||||||
switch (this.protocol) {
|
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
||||||
case Protocols.VLESS:
|
return ["tcp", "http", "grpc"].includes(this.network);
|
||||||
case Protocols.TROJAN:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.network) {
|
|
||||||
case "tcp":
|
|
||||||
case "http":
|
|
||||||
case "grpc":
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableStream() {
|
canEnableStream() {
|
||||||
switch (this.protocol) {
|
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||||
case Protocols.VMESS:
|
|
||||||
case Protocols.VLESS:
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canSniffing() {
|
canSniffing() {
|
||||||
switch (this.protocol) {
|
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||||
case Protocols.VMESS:
|
|
||||||
case Protocols.VLESS:
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
@@ -1096,19 +996,20 @@ class Inbound extends XrayCommonClass {
|
|||||||
this.sniffing = new Sniffing();
|
this.sniffing = new Sniffing();
|
||||||
}
|
}
|
||||||
|
|
||||||
genVmessLink(address='', remark='', clientIndex=0) {
|
genVmessLink(address='', port=this.port, forceTls, remark='', clientId) {
|
||||||
if (this.protocol !== Protocols.VMESS) {
|
if (this.protocol !== Protocols.VMESS) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
let obj = {
|
let obj = {
|
||||||
v: '2',
|
v: '2',
|
||||||
ps: remark,
|
ps: remark,
|
||||||
add: address,
|
add: address,
|
||||||
port: this.port,
|
port: port,
|
||||||
id: this.settings.vmesses[clientIndex].id,
|
id: clientId,
|
||||||
net: this.stream.network,
|
net: this.stream.network,
|
||||||
type: 'none',
|
type: 'none',
|
||||||
tls: this.stream.security,
|
tls: security,
|
||||||
};
|
};
|
||||||
let network = this.stream.network;
|
let network = this.stream.network;
|
||||||
if (network === 'tcp') {
|
if (network === 'tcp') {
|
||||||
@@ -1148,12 +1049,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stream.security === 'tls') {
|
if (security === 'tls') {
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
||||||
obj.add = this.stream.tls.server;
|
obj.sni = this.stream.tls.sni;
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.settings.serverName)){
|
|
||||||
obj.sni = this.stream.tls.settings.serverName;
|
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
|
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
|
||||||
obj.fp = this.stream.tls.settings.fingerprint;
|
obj.fp = this.stream.tls.settings.fingerprint;
|
||||||
@@ -1169,11 +1067,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
genVLESSLink(address = '', remark='', clientIndex=0) {
|
genVLESSLink(address = '', port=this.port, forceTls, remark='', clientId, flow) {
|
||||||
const settings = this.settings;
|
const uuid = clientId;
|
||||||
const uuid = settings.vlesses[clientIndex].id;
|
|
||||||
const port = this.port;
|
|
||||||
const type = this.stream.network;
|
const type = this.stream.network;
|
||||||
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -1224,25 +1121,24 @@ class Inbound extends XrayCommonClass {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tls) {
|
if (security === 'tls') {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
params.set("fp" , this.stream.tls.settings.fingerprint);
|
if (this.stream.isTls){
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
if(this.stream.tls.settings.allowInsecure){
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
params.set("allowInsecure", "1");
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
}
|
params.set("allowInsecure", "1");
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
}
|
||||||
address = this.stream.tls.server;
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
||||||
}
|
params.set("sni", this.stream.tls.sni);
|
||||||
if (this.stream.tls.settings.serverName !== ''){
|
}
|
||||||
params.set("sni", this.stream.tls.settings.serverName);
|
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
|
||||||
}
|
params.set("flow", flow);
|
||||||
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
}
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
if (security === 'reality') {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
@@ -1252,14 +1148,11 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (this.stream.reality.shortIds.length > 0) {
|
if (this.stream.reality.shortIds.length > 0) {
|
||||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
|
||||||
address = this.stream.reality.settings.serverName;
|
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
params.set("spx", this.stream.reality.settings.spiderX);
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
}
|
}
|
||||||
if (this.stream.network === 'tcp' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) {
|
if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) {
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", flow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1272,10 +1165,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genSSLink(address='', remark='', clientIndex = 0) {
|
genSSLink(address='', port=this.port, forceTls, remark='', clientPassword) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
const port = this.port;
|
|
||||||
const type = this.stream.network;
|
const type = this.stream.network;
|
||||||
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -1326,11 +1219,26 @@ class Inbound extends XrayCommonClass {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (security === 'tls') {
|
||||||
|
params.set("security", "tls");
|
||||||
|
if (this.stream.isTls){
|
||||||
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
|
params.set("allowInsecure", "1");
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
||||||
|
params.set("sni", this.stream.tls.sni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let password = new Array();
|
let password = new Array();
|
||||||
if (this.isSS2022) password.push(settings.password);
|
if (this.isSS2022) password.push(settings.password);
|
||||||
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
|
if (this.isSSMultiUser) password.push(clientPassword);
|
||||||
|
|
||||||
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
|
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${port}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
url.searchParams.set(key, value)
|
url.searchParams.set(key, value)
|
||||||
@@ -1339,9 +1247,8 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
genTrojanLink(address = '', port=this.port, forceTls, remark = '', clientPassword) {
|
||||||
let settings = this.settings;
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
const port = this.port;
|
|
||||||
const type = this.stream.network;
|
const type = this.stream.network;
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
@@ -1393,22 +1300,21 @@ class Inbound extends XrayCommonClass {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tls) {
|
if (security === 'tls') {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
params.set("fp" , this.stream.tls.settings.fingerprint);
|
if (this.stream.isTls){
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
if(this.stream.tls.settings.allowInsecure){
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
params.set("allowInsecure", "1");
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
|
params.set("allowInsecure", "1");
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
||||||
|
params.set("sni", this.stream.tls.sni);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
|
||||||
address = this.stream.tls.server;
|
|
||||||
}
|
|
||||||
if (this.stream.tls.settings.serverName !== ''){
|
|
||||||
params.set("sni", this.stream.tls.settings.serverName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
if (security === 'reality') {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
@@ -1418,15 +1324,12 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (this.stream.reality.shortIds.length > 0) {
|
if (this.stream.reality.shortIds.length > 0) {
|
||||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
|
||||||
address = this.stream.reality.settings.serverName;
|
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
params.set("spx", this.stream.reality.settings.spiderX);
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
|
const link = `trojan://${clientPassword}@${address}:${port}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
url.searchParams.set(key, value)
|
url.searchParams.set(key, value)
|
||||||
@@ -1435,38 +1338,63 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(address='', remark='', clientIndex=0) {
|
genLink(address='', port=this.port, forceTls='same', remark='', client) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
return this.genVmessLink(address, remark, clientIndex);
|
return this.genVmessLink(address, port, forceTls, remark, client.id);
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
return this.genVLESSLink(address, remark, clientIndex);
|
return this.genVLESSLink(address, port, forceTls, remark, client.id, client.flow);
|
||||||
case Protocols.SHADOWSOCKS:
|
case Protocols.SHADOWSOCKS:
|
||||||
return this.genSSLink(address, remark, clientIndex);
|
return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : '');
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
return this.genTrojanLink(address, remark, clientIndex);
|
return this.genTrojanLink(address, port, forceTls, remark, client.password);
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genInboundLinks(address = '', remark = '') {
|
genAllLinks(remark='', remarkModel = '-ieo', client){
|
||||||
let link = '';
|
let result = [];
|
||||||
switch (this.protocol) {
|
let email = client ? client.email : '';
|
||||||
case Protocols.VMESS:
|
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
||||||
case Protocols.VLESS:
|
let port = this.port;
|
||||||
case Protocols.TROJAN:
|
const separationChar = remarkModel.charAt(0);
|
||||||
case Protocols.SHADOWSOCKS:
|
const orderChars = remarkModel.slice(1);
|
||||||
JSON.parse(this.settings).clients.forEach((client,index) => {
|
let orders = {
|
||||||
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
'i': remark,
|
||||||
this.stream.tls.settings.domains.forEach((domain) => {
|
'e': client ? client.email : '',
|
||||||
link += this.genLink(domain.domain, [remark, client.email, domain.remark].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
'o': '',
|
||||||
});
|
};
|
||||||
} else {
|
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
||||||
link += this.genLink(address, [remark, client.email].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar);
|
||||||
}
|
result.push({
|
||||||
|
remark: r,
|
||||||
|
link: this.genLink(addr, port, 'same', r, client)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.stream.externalProxy.forEach((ep) => {
|
||||||
|
orders['o'] = ep.remark;
|
||||||
|
let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar);
|
||||||
|
result.push({
|
||||||
|
remark: r,
|
||||||
|
link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client)
|
||||||
});
|
});
|
||||||
return link;
|
});
|
||||||
default: return '';
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
||||||
|
if(this.clients){
|
||||||
|
let links = [];
|
||||||
|
this.clients.forEach((client) => {
|
||||||
|
this.genAllLinks(remark,remarkModel,client).forEach(l => {
|
||||||
|
links.push(l.link);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return links.join('\r\n');
|
||||||
|
} else {
|
||||||
|
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, 'same', remark);
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1485,7 +1413,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
let streamSettings;
|
let streamSettings;
|
||||||
if (this.canEnableStream() || this.protocol === Protocols.TROJAN) {
|
if (this.canEnableStream()) {
|
||||||
streamSettings = this.stream.toJson();
|
streamSettings = this.stream.toJson();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -1577,7 +1505,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
@@ -1586,6 +1514,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -1597,6 +1526,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
get _expiryTime() {
|
get _expiryTime() {
|
||||||
@@ -1633,7 +1563,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
fallbacks=[],) {
|
fallbacks=[],) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.vlesses = vlesses;
|
this.vlesses = vlesses;
|
||||||
this.decryption = 'none'; // Using decryption is not implemented here
|
this.decryption = decryption;
|
||||||
this.fallbacks = fallbacks;
|
this.fallbacks = fallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1665,7 +1595,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
|
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -1675,6 +1605,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -1687,6 +1618,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1763,11 +1695,11 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
this.fallbacks = fallbacks;
|
this.fallbacks = fallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
addTrojanFallback() {
|
addFallback() {
|
||||||
this.fallbacks.push(new Inbound.TrojanSettings.Fallback());
|
this.fallbacks.push(new Inbound.TrojanSettings.Fallback());
|
||||||
}
|
}
|
||||||
|
|
||||||
delTrojanFallback(index) {
|
delFallback(index) {
|
||||||
this.fallbacks.splice(index, 1);
|
this.fallbacks.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1786,7 +1718,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomSeq(10), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(password=RandomUtil.randomSeq(10), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
@@ -1795,6 +1727,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -1806,6 +1739,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
reset: this.reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1818,6 +1752,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1922,7 +1857,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
@@ -1932,6 +1867,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -1944,6 +1880,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
reset: this.reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1957,6 +1894,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,15 @@ function safeBase64(str) {
|
|||||||
|
|
||||||
function formatSecond(second) {
|
function formatSecond(second) {
|
||||||
if (second < 60) {
|
if (second < 60) {
|
||||||
return second.toFixed(0) + ' s';
|
return second.toFixed(0) + 's';
|
||||||
} else if (second < 3600) {
|
} else if (second < 3600) {
|
||||||
return (second / 60).toFixed(0) + ' m';
|
return (second / 60).toFixed(0) + 'm';
|
||||||
} else if (second < 3600 * 24) {
|
} else if (second < 3600 * 24) {
|
||||||
return (second / 3600).toFixed(0) + ' h';
|
return (second / 3600).toFixed(0) + 'h';
|
||||||
} else {
|
} else {
|
||||||
return (second / 3600 / 24).toFixed(0) + ' d';
|
day = Math.floor(second / 3600 / 24);
|
||||||
|
remain = ((second/3600) - (day*24)).toFixed(0);
|
||||||
|
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ function addZero(num) {
|
|||||||
|
|
||||||
function toFixed(num, n) {
|
function toFixed(num, n) {
|
||||||
n = Math.pow(10, n);
|
n = Math.pow(10, n);
|
||||||
return Math.round(num * n) / n;
|
return Math.floor(num * n) / n;
|
||||||
}
|
}
|
||||||
|
|
||||||
function debounce(fn, delay) {
|
function debounce(fn, delay) {
|
||||||
@@ -94,11 +96,13 @@ function setCookie(cname, cvalue, exdays) {
|
|||||||
function usageColor(data, threshold, total) {
|
function usageColor(data, threshold, total) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case data === null:
|
case data === null:
|
||||||
|
return "green";
|
||||||
|
case total < 0:
|
||||||
return "blue";
|
return "blue";
|
||||||
case total <= 0:
|
case total == 0:
|
||||||
return "blue";
|
return "purple";
|
||||||
case data < total - threshold:
|
case data < total - threshold:
|
||||||
return "cyan";
|
return "blue";
|
||||||
case data < total:
|
case data < total:
|
||||||
return "orange";
|
return "orange";
|
||||||
default:
|
default:
|
||||||
@@ -106,6 +110,41 @@ function usageColor(data, threshold, total) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clientUsageColor(clientStats, trafficDiff) {
|
||||||
|
switch (true) {
|
||||||
|
case !clientStats || clientStats.total == 0:
|
||||||
|
return "#7a316f"; // Purple
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total - trafficDiff:
|
||||||
|
return "#0e49b5"; // Blue
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total:
|
||||||
|
return "#f37b24"; // Orange
|
||||||
|
default:
|
||||||
|
return "#e04141"; // red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function userExpiryColor(threshold, client, isDark = false) {
|
||||||
|
if (!client.enable) {
|
||||||
|
return isDark ? '#2c3950' : '#bcbcbc'; // Gray
|
||||||
|
}
|
||||||
|
now = new Date().getTime(),
|
||||||
|
expiry = client.expiryTime;
|
||||||
|
switch (true) {
|
||||||
|
case expiry === null:
|
||||||
|
return "#7a316f"; // Purple
|
||||||
|
case expiry < 0:
|
||||||
|
return "#0e49b5"; // Blue
|
||||||
|
case expiry == 0:
|
||||||
|
return "#7a316f"; // Purple
|
||||||
|
case now < expiry - threshold:
|
||||||
|
return "#0e49b5"; // Blue
|
||||||
|
case now < expiry:
|
||||||
|
return "#f37b24"; // Orange
|
||||||
|
default:
|
||||||
|
return "#e04141"; // red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function doAllItemsExist(array1, array2) {
|
function doAllItemsExist(array1, array2) {
|
||||||
for (let i = 0; i < array1.length; i++) {
|
for (let i = 0; i < array1.length; i++) {
|
||||||
if (!array2.includes(array1[i])) {
|
if (!array2.includes(array1[i])) {
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
class Msg {
|
||||||
|
constructor(success, msg, obj) {
|
||||||
|
this.success = false;
|
||||||
|
this.msg = "";
|
||||||
|
this.obj = null;
|
||||||
|
|
||||||
|
if (success != null) {
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
if (msg != null) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
if (obj != null) {
|
||||||
|
this.obj = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class HttpUtil {
|
class HttpUtil {
|
||||||
static _handleMsg(msg) {
|
static _handleMsg(msg) {
|
||||||
if (!(msg instanceof Msg)) {
|
if (!(msg instanceof Msg)) {
|
||||||
@@ -159,7 +177,7 @@ class ObjectUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return obj.toString().toLowerCase().indexOf(key.toLowerCase()) >= 0;
|
return this.isEmpty(obj) ? false : obj.toString().toLowerCase().indexOf(key.toLowerCase()) >= 0;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
g.GET("/createbackup", a.createBackup)
|
g.GET("/createbackup", a.createBackup)
|
||||||
|
g.POST("/onlines", a.onlines)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
}
|
}
|
||||||
@@ -95,3 +96,7 @@ func (a *APIController) delDepletedClients(c *gin.Context) {
|
|||||||
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
@@ -35,7 +36,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
g.POST("/import", a.importInbound)
|
||||||
|
g.POST("/onlines", a.onlines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getInbounds(c *gin.Context) {
|
func (a *InboundController) getInbounds(c *gin.Context) {
|
||||||
@@ -253,3 +255,32 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonMsg(c, "All delpeted clients are deleted", nil)
|
jsonMsg(c, "All delpeted clients are deleted", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) importInbound(c *gin.Context) {
|
||||||
|
inbound := &model.Inbound{}
|
||||||
|
err := json.Unmarshal([]byte(c.PostForm("data")), inbound)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := session.GetLoginUser(c)
|
||||||
|
inbound.Id = 0
|
||||||
|
inbound.UserId = user.Id
|
||||||
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
|
|
||||||
|
for index := range inbound.ClientStats {
|
||||||
|
inbound.ClientStats[index].Id = 0
|
||||||
|
inbound.ClientStats[index].Enable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
needRestart := false
|
||||||
|
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
||||||
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
|
if err == nil && needRestart {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) onlines(c *gin.Context) {
|
||||||
|
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,44 +50,11 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
type settingFunc func() (interface{}, error)
|
result, err := a.settingService.GetDefaultSettings(c.Request.Host)
|
||||||
|
if err != nil {
|
||||||
settings := map[string]settingFunc{
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
"expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() },
|
return
|
||||||
"trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() },
|
|
||||||
"defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() },
|
|
||||||
"defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() },
|
|
||||||
"tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() },
|
|
||||||
"subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() },
|
|
||||||
"subPort": func() (interface{}, error) { return a.settingService.GetSubPort() },
|
|
||||||
"subPath": func() (interface{}, error) { return a.settingService.GetSubPath() },
|
|
||||||
"subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
|
|
||||||
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
|
||||||
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
|
||||||
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
|
|
||||||
"subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
|
|
||||||
for key, fn := range settings {
|
|
||||||
value, err := fn()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
subTLS := false
|
|
||||||
if result["subKeyFile"] != "" || result["subCertFile"] != "" {
|
|
||||||
subTLS = true
|
|
||||||
}
|
|
||||||
result["subTLS"] = subTLS
|
|
||||||
|
|
||||||
delete(result, "subKeyFile")
|
|
||||||
delete(result, "subCertFile")
|
|
||||||
|
|
||||||
jsonObj(c, result, nil)
|
jsonObj(c, result, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
63
web/controller/xray_setting.go
Normal file
63
web/controller/xray_setting.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XraySettingController struct {
|
||||||
|
XraySettingService service.XraySettingService
|
||||||
|
SettingService service.SettingService
|
||||||
|
InboundService service.InboundService
|
||||||
|
XrayService service.XrayService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
|
||||||
|
a := &XraySettingController{}
|
||||||
|
a.initRouter(g)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
||||||
|
g = g.Group("/xray")
|
||||||
|
|
||||||
|
g.POST("/", a.getXraySetting)
|
||||||
|
g.POST("/update", a.updateSetting)
|
||||||
|
g.GET("/getXrayResult", a.getXrayResult)
|
||||||
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
|
xraySetting, err := a.SettingService.GetXrayConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inboundTags, err := a.InboundService.GetInboundTags()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xrayResponse := "{ \"xraySetting\": " + xraySetting + ", \"inboundTags\": " + inboundTags + " }"
|
||||||
|
jsonObj(c, xrayResponse, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) updateSetting(c *gin.Context) {
|
||||||
|
xraySetting := c.PostForm("xraySetting")
|
||||||
|
err := a.XraySettingService.SaveXraySetting(xraySetting)
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||||
|
defaultJsonConfig, err := a.SettingService.GetDefaultXrayConfig()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getXrayResult(c *gin.Context) {
|
||||||
|
jsonObj(c, a.XrayService.GetXrayResult(), nil)
|
||||||
|
}
|
||||||
@@ -7,8 +7,9 @@ import (
|
|||||||
type XUIController struct {
|
type XUIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
settingController *SettingController
|
settingController *SettingController
|
||||||
|
xraySettingController *XraySettingController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXUIController(g *gin.RouterGroup) *XUIController {
|
func NewXUIController(g *gin.RouterGroup) *XUIController {
|
||||||
@@ -24,9 +25,11 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.GET("/", a.index)
|
g.GET("/", a.index)
|
||||||
g.GET("/inbounds", a.inbounds)
|
g.GET("/inbounds", a.inbounds)
|
||||||
g.GET("/settings", a.settings)
|
g.GET("/settings", a.settings)
|
||||||
|
g.GET("/xray", a.xraySettings)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
a.settingController = NewSettingController(g)
|
a.settingController = NewSettingController(g)
|
||||||
|
a.xraySettingController = NewXraySettingController(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XUIController) index(c *gin.Context) {
|
func (a *XUIController) index(c *gin.Context) {
|
||||||
@@ -40,3 +43,7 @@ func (a *XUIController) inbounds(c *gin.Context) {
|
|||||||
func (a *XUIController) settings(c *gin.Context) {
|
func (a *XUIController) settings(c *gin.Context) {
|
||||||
html(c, "settings.html", "pages.settings.title", nil)
|
html(c, "settings.html", "pages.settings.title", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *XUIController) xraySettings(c *gin.Context) {
|
||||||
|
html(c, "xray.html", "pages.xray.title", nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package entity
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
"x-ui/xray"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
@@ -16,46 +14,38 @@ type Msg struct {
|
|||||||
Obj interface{} `json:"obj"`
|
Obj interface{} `json:"obj"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pager struct {
|
|
||||||
Current int `json:"current"`
|
|
||||||
PageSize int `json:"page_size"`
|
|
||||||
Total int `json:"total"`
|
|
||||||
OrderBy string `json:"order_by"`
|
|
||||||
Desc bool `json:"desc"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
List interface{} `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AllSetting struct {
|
type AllSetting struct {
|
||||||
WebListen string `json:"webListen" form:"webListen"`
|
WebListen string `json:"webListen" form:"webListen"`
|
||||||
WebDomain string `json:"webDomain" form:"webDomain"`
|
WebDomain string `json:"webDomain" form:"webDomain"`
|
||||||
WebPort int `json:"webPort" form:"webPort"`
|
WebPort int `json:"webPort" form:"webPort"`
|
||||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||||
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
PageSize int `json:"pageSize" form:"pageSize"`
|
||||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||||
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
RemarkModel string `json:"remarkModel" form:"remarkModel"`
|
||||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
TgLang string `json:"tgLang" form:"tgLang"`
|
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
SubListen string `json:"subListen" form:"subListen"`
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
SubPort int `json:"subPort" form:"subPort"`
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
SubPath string `json:"subPath" form:"subPath"`
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
SubDomain string `json:"subDomain" form:"subDomain"`
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
|
SubURI string `json:"subURI" form:"subURI"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
@@ -106,13 +96,14 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
s.WebBasePath += "/"
|
s.WebBasePath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
xrayConfig := &xray.Config{}
|
if !strings.HasPrefix(s.SubPath, "/") {
|
||||||
err := json.Unmarshal([]byte(s.XrayTemplateConfig), xrayConfig)
|
s.SubPath = "/" + s.SubPath
|
||||||
if err != nil {
|
}
|
||||||
return common.NewError("xray template config invalid:", err)
|
if !strings.HasSuffix(s.SubPath, "/") {
|
||||||
|
s.SubPath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
_, 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta name="renderer" content="webkit">
|
<meta name="renderer" content="webkit">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/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=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
|
||||||
@@ -13,6 +13,20 @@
|
|||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
/* vazirmatn-regular - arabic_latin_latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('{{ .base_path }}assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2');
|
||||||
|
unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
|
||||||
|
'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
|
||||||
|
'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
{{define "js"}}
|
{{define "js"}}
|
||||||
<script src="{{ .base_path }}assets/vue@2.6.12/vue.min.js"></script>
|
<script src="{{ .base_path }}assets/vue@2.6.12/vue.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
|
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.js"></script>
|
<script src="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
|
||||||
<script src="{{ .base_path }}assets/axios/axios.min.js"></script>
|
<script src="{{ .base_path }}assets/axios/axios.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
|
|
||||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
|
||||||
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
|
|
||||||
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
|
||||||
<script src="{{ .base_path }}assets/js/model/models.js?{{ .cur_ver }}"></script>
|
|
||||||
<script src="{{ .base_path }}assets/js/langs.js"></script>
|
<script src="{{ .base_path }}assets/js/langs.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const basePath = '{{ .base_path }}';
|
const basePath = '{{ .base_path }}';
|
||||||
|
|||||||
@@ -1,8 +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"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
|
||||||
<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"
|
||||||
:autosize="{minRows: 10, maxRows: 20}"
|
:autosize="{minRows: 10, maxRows: 20}"
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
{{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"
|
:closable="true"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="300px">
|
width="300px" :class="themeSwitcher.currentTheme">
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
||||||
<template v-if="app.subSettings.enable && qrModal.subId">
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
<a-divider>Subscription</a-divider>
|
<a-divider>Subscription</a-divider>
|
||||||
@@ -11,7 +10,7 @@
|
|||||||
</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="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
|
<a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -20,39 +19,25 @@
|
|||||||
|
|
||||||
const qrModal = {
|
const qrModal = {
|
||||||
title: '',
|
title: '',
|
||||||
clientIndex: 0,
|
|
||||||
inbound: new Inbound(),
|
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
client: null,
|
client: null,
|
||||||
qrcodes: [],
|
qrcodes: [],
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
subId: '',
|
subId: '',
|
||||||
show: function (title = '', dbInbound = new DBInbound(), clientIndex = 0) {
|
show: function (title = '', dbInbound, client) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.clientIndex = clientIndex;
|
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
settings = JSON.parse(this.inbound.settings);
|
this.client = client;
|
||||||
this.client = settings.clients[clientIndex];
|
|
||||||
remark = [this.dbInbound.remark, ( this.client ? this.client.email : '')].filter(Boolean).join('-');
|
|
||||||
address = this.dbInbound.address;
|
|
||||||
this.subId = '';
|
this.subId = '';
|
||||||
this.qrcodes = [];
|
this.qrcodes = [];
|
||||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
||||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
|
||||||
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
|
||||||
this.qrcodes.push({
|
|
||||||
remark: remarkText,
|
|
||||||
link: this.inbound.genLink(domain.domain, remarkText, clientIndex)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.qrcodes.push({
|
this.qrcodes.push({
|
||||||
remark: remark,
|
remark: l.remark,
|
||||||
link: this.inbound.genLink(address, remark, clientIndex)
|
link: l.link
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
@@ -84,8 +69,7 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
return app.subSettings.subURI+subID+'?name='+subID;
|
||||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+subID });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{{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" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}" :class="themeSwitcher.currentTheme">
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||||
: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">
|
||||||
@@ -31,7 +30,10 @@
|
|||||||
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
||||||
text: () => this.content,
|
text: () => this.content,
|
||||||
});
|
});
|
||||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
this.clipboard.on('success', () => {
|
||||||
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,64 +2,241 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<style>
|
<style>
|
||||||
|
h1 {
|
||||||
#app {
|
text-align: center;
|
||||||
padding-top: 100px;
|
margin: 20px 0 50px 0;
|
||||||
|
}
|
||||||
|
.ant-btn,
|
||||||
|
.ant-input {
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
.ant-input-group-addon {
|
||||||
|
border-radius: 0 30px 30px 0;
|
||||||
|
width: 50px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.ant-input-affix-wrapper .ant-input-prefix {
|
||||||
|
left: 23px;
|
||||||
|
}
|
||||||
|
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
.centered {
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#login {
|
||||||
|
animation: charge 0.5s both;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 3rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
#login:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||||
|
}
|
||||||
|
@keyframes charge {
|
||||||
|
from {
|
||||||
|
transform: translateY(5rem);
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
to {
|
||||||
h1 {
|
transform: translateY(0);
|
||||||
text-align: center;
|
opacity: 1;
|
||||||
margin: 20px 0 50px 0;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.ant-btn, .ant-input {
|
@keyframes wave {
|
||||||
height: 50px;
|
from {
|
||||||
border-radius: 30px;
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
to {
|
||||||
.ant-input-group-addon {
|
transform: rotate(360deg);
|
||||||
border-radius: 0 30px 30px 0;
|
|
||||||
width: 50px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.ant-input-affix-wrapper .ant-input-prefix {
|
.wave {
|
||||||
left: 23px;
|
opacity: 0.6;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 40%;
|
||||||
|
left: 50%;
|
||||||
|
width: 6000px;
|
||||||
|
height: 6000px;
|
||||||
|
background-color: rgba(14, 73, 181, 0.08);
|
||||||
|
margin-left: -3000px;
|
||||||
|
transform-origin: 50% 48%;
|
||||||
|
border-radius: 46%;
|
||||||
|
animation: wave 72s infinite linear;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.wave2 {
|
||||||
|
animation: wave 88s infinite linear;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
.wave3 {
|
||||||
|
animation: wave 80s infinite linear;
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
.under {
|
||||||
|
background-color: #dce9f5;
|
||||||
|
}
|
||||||
|
.dark .wave {
|
||||||
|
background: rgba(14, 73, 181, 0.2);
|
||||||
|
}
|
||||||
|
.dark .under {
|
||||||
|
background-color: #101828;
|
||||||
|
}
|
||||||
|
.dark #login {
|
||||||
|
background-color: #151f31;
|
||||||
|
}
|
||||||
|
.dark h1 {
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
.ant-btn-primary-login {
|
||||||
|
color: #0e49b5;
|
||||||
|
background-color: #edf4fa;
|
||||||
|
border-color: #a9c5e7;
|
||||||
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
box-shadow: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.ant-btn-primary-login:focus,
|
||||||
|
.ant-btn-primary-login:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0c3f9d;
|
||||||
|
border-color: #0e49b5;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
270deg,
|
||||||
|
rgba(123, 199, 77, 0) 30%,
|
||||||
|
#2f67c2,
|
||||||
|
rgba(123, 199, 77, 0) 100%
|
||||||
|
);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
animation: ma-bg-move ease-in-out 5s infinite;
|
||||||
|
background-position-x: -500px;
|
||||||
|
width: 95%;
|
||||||
|
animation-delay: -0.5s;
|
||||||
|
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
|
||||||
|
}
|
||||||
|
.ant-btn-primary-login.active,
|
||||||
|
.ant-btn-primary-login:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #04308f;
|
||||||
|
border-color: #04308f;
|
||||||
|
}
|
||||||
|
@keyframes ma-bg-move {
|
||||||
|
0% {
|
||||||
|
background-position: -500px 0;
|
||||||
}
|
}
|
||||||
|
50% {
|
||||||
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
background-position: 1000px 0;
|
||||||
padding-left: 50px;
|
|
||||||
}
|
}
|
||||||
|
100% {
|
||||||
.centered {
|
background-position: 1000px 0;
|
||||||
display: flex;
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.title {
|
.wave-btn-bg {
|
||||||
font-size: 32px;
|
position: relative;
|
||||||
font-weight: bold;
|
border-radius: 25px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg {
|
||||||
|
color: #fff;
|
||||||
|
position: relative;
|
||||||
|
background-color: #0e49b5;
|
||||||
|
border: 2px double transparent;
|
||||||
|
background-origin: border-box;
|
||||||
|
background-clip: padding-box, border-box;
|
||||||
|
background-size: 300%;
|
||||||
|
animation: wave-btn-tara 4s ease infinite;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg-cl {
|
||||||
|
background-image: linear-gradient(rgba(13, 14, 33, 0), rgba(13, 14, 33, 0)),
|
||||||
|
radial-gradient(circle at left top, #0e49b5, #387eff, #0e49b5) !important;
|
||||||
|
border-radius: 3em;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg-cl:hover {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg-cl:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -5px;
|
||||||
|
left: -5px;
|
||||||
|
bottom: -5px;
|
||||||
|
right: -5px;
|
||||||
|
z-index: -1;
|
||||||
|
background: inherit;
|
||||||
|
background-size: inherit;
|
||||||
|
border-radius: 4em;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg-cl:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
filter: blur(20px);
|
||||||
|
animation: wave-btn-tara 8s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes wave-btn-tara {
|
||||||
|
to {
|
||||||
|
background-position: 300%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.dark .ant-btn-primary-login {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
rgba(13, 14, 33, 0.45),
|
||||||
|
rgba(13, 14, 33, 0.35)
|
||||||
|
);
|
||||||
|
border-radius: 2rem;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
height: 46px;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
padding: 0 15px;
|
||||||
|
width: 100%;
|
||||||
|
animation: none;
|
||||||
|
background-position-x: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.darkCardClass">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content>
|
<a-layout-content class="under" style="min-height: 0;">
|
||||||
|
<div class='wave'></div>
|
||||||
|
<div class='wave wave2'></div>
|
||||||
|
<div class='wave wave3'></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 :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
<a-col>
|
||||||
<h1 class="title" :style="themeSwitcher.textStyle">{{ i18n "pages.login.title" }}</h1>
|
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
<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 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;' + themeSwitcher.textStyle"/>
|
<a-icon slot="prefix" type="user" style="font-size: 16px;"/>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -69,16 +246,18 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<a-button type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
|
<div class="wave-btn-bg wave-btn-bg-cl">
|
||||||
:style="loading ? { width: '50px' } : { display: 'block', width: '100%' }">
|
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
|
||||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
:style="loading ? { width: '50px' } : { display: 'inline-block' }">
|
||||||
</a-button>
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<a-col :span="12">
|
<a-col :span="24">
|
||||||
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
<a-select-option :value="l.value" label="English" 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>
|
||||||
@@ -89,12 +268,19 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<theme-switch />
|
<a-col>
|
||||||
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
|
</a-col>
|
||||||
|
<a-col>
|
||||||
|
<theme-switch />
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</transition>
|
</transition>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
@@ -102,6 +288,13 @@
|
|||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "component/password" .}}
|
{{template "component/password" .}}
|
||||||
<script>
|
<script>
|
||||||
|
class User {
|
||||||
|
constructor() {
|
||||||
|
this.username = "";
|
||||||
|
this.password = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -112,7 +305,6 @@
|
|||||||
lang: ""
|
lang: ""
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.updateBackground();
|
|
||||||
this.lang = getLang();
|
this.lang = getLang();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -123,17 +315,9 @@
|
|||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
location.href = basePath + 'xui/';
|
location.href = basePath + 'xui/';
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
updateBackground() {
|
|
||||||
document.querySelector('#app').style.background = colors[this.themeSwitcher.currentTheme].bg;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'themeSwitcher.isDarkTheme'(newVal, oldVal) {
|
|
||||||
this.updateBackground();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
{{define "clientsBulkModal"}}
|
{{define "clientsBulkModal"}}
|
||||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
||||||
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "pages.client.method" }}</td>
|
<td>{{ i18n "pages.client.method" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
|
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option :value="0">Random</a-select-option>
|
<a-select-option :value="0">Random</a-select-option>
|
||||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||||
@@ -64,7 +62,7 @@
|
|||||||
<td>Flow</td>
|
<td>Flow</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -72,7 +70,14 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="app.subSettings.enable">
|
<tr v-if="app.subSettings.enable">
|
||||||
<td>Subscription</td>
|
<td>Subscription
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
|
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
|
||||||
@@ -80,7 +85,14 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="app.tgBotEnable">
|
<tr v-if="app.tgBotEnable">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram ID
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
|
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
|
||||||
@@ -132,11 +144,27 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-if="clientsBulkModal.expiryTime != 0">
|
||||||
|
<td>
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -162,6 +190,7 @@
|
|||||||
tgId: "",
|
tgId: "",
|
||||||
flow: "",
|
flow: "",
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
|
reset: 0,
|
||||||
ok() {
|
ok() {
|
||||||
clients = [];
|
clients = [];
|
||||||
method=clientsBulkModal.emailMethod;
|
method=clientsBulkModal.emailMethod;
|
||||||
@@ -186,6 +215,7 @@
|
|||||||
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
||||||
newClient.flow = clientsBulkModal.flow;
|
newClient.flow = clientsBulkModal.flow;
|
||||||
}
|
}
|
||||||
|
newClient.reset = clientsBulkModal.reset;
|
||||||
clients.push(newClient);
|
clients.push(newClient);
|
||||||
}
|
}
|
||||||
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||||
@@ -209,6 +239,7 @@
|
|||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
|
this.reset = 0;
|
||||||
},
|
},
|
||||||
newClient(protocol) {
|
newClient(protocol) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{{define "clientsModal"}}
|
{{define "clientsModal"}}
|
||||||
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
||||||
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
<template v-if="isEdit">
|
||||||
|
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||||
|
</template>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
@@ -34,7 +36,7 @@
|
|||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
this.clients = this.inbound.clients;
|
||||||
this.index = index === null ? this.clients.length : index;
|
this.index = index === null ? this.clients.length : index;
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
if (isEdit){
|
if (isEdit){
|
||||||
@@ -48,15 +50,6 @@
|
|||||||
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
|
||||||
switch(protocol){
|
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
|
||||||
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getClientId(protocol, client) {
|
getClientId(protocol, client) {
|
||||||
switch(protocol){
|
switch(protocol){
|
||||||
case Protocols.TROJAN: return client.password;
|
case Protocols.TROJAN: return client.password;
|
||||||
@@ -108,8 +101,11 @@
|
|||||||
get isExpiry() {
|
get isExpiry() {
|
||||||
return this.clientModal.isEdit && this.client.expiryTime >0 ? (this.client.expiryTime < new Date().getTime()) : false;
|
return this.clientModal.isEdit && this.client.expiryTime >0 ? (this.client.expiryTime < new Date().getTime()) : false;
|
||||||
},
|
},
|
||||||
get statsColor() {
|
get delayedStart() {
|
||||||
return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB);
|
return this.clientModal.delayedStart;
|
||||||
|
},
|
||||||
|
set delayedStart(value) {
|
||||||
|
this.clientModal.delayedStart = value;
|
||||||
},
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
@@ -123,7 +119,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>{{ i18n "menu.settings"}}</span>
|
<span>{{ i18n "menu.settings"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="{{ .base_path }}xui/xray">
|
||||||
|
<a-icon type="tool"></a-icon>
|
||||||
|
<span>{{ i18n "menu.xray"}}</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>{{ i18n "menu.logout"}}</span>
|
||||||
@@ -22,7 +26,7 @@
|
|||||||
<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="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch />
|
<theme-switch />
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
@@ -31,17 +35,16 @@
|
|||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
<a-drawer id="sider-drawer" placement="left" :closable="false" :class="themeSwitcher.currentTheme"
|
||||||
@close="siderDrawer.close()"
|
@close="siderDrawer.close()"
|
||||||
:visible="siderDrawer.visible"
|
:visible="siderDrawer.visible"
|
||||||
:wrap-class-name="themeSwitcher.darkDrawerClass"
|
|
||||||
:wrap-style="{ padding: 0 }">
|
:wrap-style="{ padding: 0 }">
|
||||||
<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="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch />
|
<theme-switch />
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@input="$emit('input', $event.target.value)">
|
@input="$emit('input', $event.target.value)">
|
||||||
<template v-if="icon" #prefix>
|
<template v-if="icon" #prefix>
|
||||||
<a-icon :type="icon" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
<a-icon :type="icon" style="font-size: 16px;" />
|
||||||
</template>
|
</template>
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
||||||
@click="toggleShowPassword"
|
@click="toggleShowPassword"
|
||||||
:style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
style="font-size: 16px;" />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template v-if="type === 'text'">
|
<template v-if="type === 'text'">
|
||||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></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" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" :step="step" 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>
|
||||||
@@ -28,7 +28,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"],
|
props: ["type", "title", "desc", "value", "min", "step", "placeholder"],
|
||||||
template: `{{template "component/settingListItem"}}`,
|
template: `{{template "component/settingListItem"}}`,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{{define "component/themeSwitchTemplate"}}
|
{{define "component/themeSwitchTemplate"}}
|
||||||
<template>
|
<template>
|
||||||
<a-switch :default-checked="themeSwitcher.isDarkTheme"
|
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||||
checked-children="☀"
|
|
||||||
un-checked-children="🌙"
|
|
||||||
@change="themeSwitcher.toggleTheme()">
|
@change="themeSwitcher.toggleTheme()">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</template>
|
</template>
|
||||||
@@ -10,39 +8,17 @@
|
|||||||
|
|
||||||
{{define "component/themeSwitcher"}}
|
{{define "component/themeSwitcher"}}
|
||||||
<script>
|
<script>
|
||||||
const colors = {
|
|
||||||
dark: {
|
|
||||||
bg: "#242c3a",
|
|
||||||
text: "hsla(0,0%,100%,.65)"
|
|
||||||
},
|
|
||||||
light: {
|
|
||||||
bg: '#f0f2f5',
|
|
||||||
text: "rgba(0, 0, 0, 0.7)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createThemeSwitcher() {
|
function createThemeSwitcher() {
|
||||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||||
const theme = isDarkTheme ? 'dark' : 'light';
|
const theme = isDarkTheme ? 'dark' : 'light';
|
||||||
return {
|
return {
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
bgStyle: `background: ${colors[theme].bg};`,
|
|
||||||
textStyle: `color: ${colors[theme].text};`,
|
|
||||||
darkClass: isDarkTheme ? 'ant-dark' : '',
|
|
||||||
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
|
|
||||||
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
|
|
||||||
get currentTheme() {
|
get currentTheme() {
|
||||||
return this.isDarkTheme ? 'dark' : 'light';
|
return this.isDarkTheme ? 'dark' : 'light';
|
||||||
},
|
},
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
this.isDarkTheme = !this.isDarkTheme;
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
this.theme = this.isDarkTheme ? 'dark' : 'light';
|
|
||||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
this.bgStyle = `background: ${colors[this.theme].bg};`;
|
|
||||||
this.textStyle = `color: ${colors[this.theme].text};`;
|
|
||||||
this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
|
|
||||||
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
|
||||||
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
{{define "form/client"}}
|
{{define "form/client"}}
|
||||||
<a-form layout="inline" v-if="client">
|
<a-form layout="inline" v-if="client">
|
||||||
<template v-if="isEdit">
|
|
||||||
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
|
||||||
</template>
|
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "pages.inbounds.enable" }}</td>
|
<td>{{ i18n "pages.inbounds.enable" }}</td>
|
||||||
@@ -47,7 +44,14 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
<tr v-if="client.email && app.subSettings.enable">
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
<td>Subscription
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
|
||||||
@@ -55,7 +59,14 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
<tr v-if="client.email && app.tgBotEnable">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram ID
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
|
<a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
|
||||||
@@ -66,7 +77,7 @@
|
|||||||
<td>Flow</td>
|
<td>Flow</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -92,7 +103,7 @@
|
|||||||
<tr v-if="isEdit && clientStats">
|
<tr v-if="isEdit && clientStats">
|
||||||
<td>{{ i18n "usage" }}</td>
|
<td>{{ i18n "usage" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag :color="statsColor">
|
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||||
[[ sizeFormat(clientStats.up) ]] /
|
[[ sizeFormat(clientStats.up) ]] /
|
||||||
[[ sizeFormat(clientStats.down) ]]
|
[[ sizeFormat(clientStats.down) ]]
|
||||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||||
@@ -107,11 +118,11 @@
|
|||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="clientModal.delayedStart">
|
<tr v-if="delayedStart">
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -132,9 +143,25 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="client.expiryTime != 0">
|
||||||
|
<td>
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<td>{{ i18n "protocol" }}</td>
|
<td>{{ i18n "protocol" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
@@ -126,6 +126,7 @@
|
|||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
<template v-if="inbound.canEnableStream()">
|
<template v-if="inbound.canEnableStream()">
|
||||||
{{template "form/streamSettings"}}
|
{{template "form/streamSettings"}}
|
||||||
|
{{template "form/externalProxy" }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
|
|||||||
544
web/html/xui/form/outbound.html
Normal file
544
web/html/xui/form/outbound.html
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
{{define "form/outbound"}}
|
||||||
|
<!-- base -->
|
||||||
|
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
|
||||||
|
<a-tab-pane key="1" tab="Form">
|
||||||
|
<a-form layout="inline">
|
||||||
|
<table width="100%" class="ant-table-tbody">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "protocol" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.xray.outbound.tag" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
|
||||||
|
<a-input v-model.trim="outbound.tag" style="width: 250px" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- freedom settings-->
|
||||||
|
<template v-if="outbound.protocol === Protocols.Freedom">
|
||||||
|
<tr>
|
||||||
|
<td>Strategy</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select
|
||||||
|
v-model="outbound.settings.domainStrategy"
|
||||||
|
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Fragment</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch
|
||||||
|
:checked="Object.keys(outbound.settings.fragment).length >0"
|
||||||
|
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-if="Object.keys(outbound.settings.fragment).length >0">
|
||||||
|
<tr>
|
||||||
|
<td>Packets</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select
|
||||||
|
v-model="outbound.settings.fragment.packets"
|
||||||
|
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Length</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.settings.fragment.length" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Interval</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.settings.fragment.interval" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- blackhole settings -->
|
||||||
|
<template v-if="outbound.protocol === Protocols.Blackhole">
|
||||||
|
<tr>
|
||||||
|
<td>Response Type</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select
|
||||||
|
v-model="outbound.settings.type"
|
||||||
|
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- dns settings -->
|
||||||
|
<template v-if="outbound.protocol === Protocols.DNS">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select
|
||||||
|
v-model="outbound.settings.network"
|
||||||
|
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Address + Port -->
|
||||||
|
<template v-if="outbound.hasAddressPort()">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.address" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.settings.address" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Vnext (vless/vmess) settings -->
|
||||||
|
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
||||||
|
<tr>
|
||||||
|
<td>ID</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.settings.id" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- vless settings -->
|
||||||
|
<template v-if="outbound.canEnableTlsFlow()">
|
||||||
|
<tr>
|
||||||
|
<td>Flow</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.settings.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||||
|
<template v-if="outbound.hasServers()">
|
||||||
|
<tr v-if="outbound.hasUsername()">
|
||||||
|
<td>{{ i18n "username" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.settings.user" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "password" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.settings.password" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- shadowsocks -->
|
||||||
|
<template v-if="outbound.protocol === Protocols.Shadowsocks">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "encryption" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>UDP over TCP</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- stream settings -->
|
||||||
|
<template v-if="outbound.canEnableStream()">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "transmission" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||||
|
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
|
<a-select-option value="kcp">KCP</a-select-option>
|
||||||
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
|
<a-select-option value="http">HTTP2</a-select-option>
|
||||||
|
<a-select-option value="quic">QUIC</a-select-option>
|
||||||
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-if="outbound.stream.network === 'tcp'">
|
||||||
|
<tr>
|
||||||
|
<td>http {{ i18n "camouflage" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch
|
||||||
|
:checked="outbound.stream.tcp.type === 'http'"
|
||||||
|
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-if="outbound.stream.tcp.type == 'http'">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "host" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.host"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "path" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.path"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- kcp -->
|
||||||
|
<template v-if="outbound.stream.network === 'kcp'">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "camouflage" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||||
|
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||||
|
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "password" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model="outbound.stream.kcp.seed" style="width: 250px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>mtu</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>tti (ms)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>uplink capacity (MB/S)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>downlink capacity (MB/S)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>congestion</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>read buffer size (MB)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>write buffer size (MB)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- ws -->
|
||||||
|
<template v-if="outbound.stream.network === 'ws'">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "host" }}</td>
|
||||||
|
<td><a-form-item><a-input style="width: 250px" v-model="outbound.stream.ws.host"></a-input></a-form-item></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "path" }}</td>
|
||||||
|
<td><a-form-item><a-input style="width: 250px;" v-model.trim="outbound.stream.ws.path"></a-input></a-form-item></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- http -->
|
||||||
|
<template v-if="outbound.stream.network === 'http'">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "host" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.stream.http.host" style="width: 250px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "path" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.stream.http.path" style="width: 250px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- quic -->
|
||||||
|
<template v-if="outbound.stream.network === 'quic'">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="none">none</a-select-option>
|
||||||
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "password" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.stream.quic.key" style="width: 250px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "camouflage" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||||
|
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||||
|
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- grpc -->
|
||||||
|
<template v-if="outbound.stream.network === 'grpc'">
|
||||||
|
<tr>
|
||||||
|
<td>serviceName</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>MultiMode</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- tls settings -->
|
||||||
|
<template v-if="outbound.canEnableTls()">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "security" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-radio-group v-model="outbound.stream.security" button-style="solid">
|
||||||
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
|
<a-radio-button value="tls">TLS</a-radio-button>
|
||||||
|
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-if="outbound.stream.isTls">
|
||||||
|
<tr>
|
||||||
|
<td>SNI</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item placeholder="Server Name Indication">
|
||||||
|
<a-input v-model.trim="outbound.stream.tls.serverName" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>uTLS</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.stream.tls.fingerprint"
|
||||||
|
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value=''>None</a-select-option>
|
||||||
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>ALPN</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select
|
||||||
|
mode="multiple"
|
||||||
|
style="width: 250px"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
v-model="outbound.stream.tls.alpn">
|
||||||
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Allow insecure</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- reality settings -->
|
||||||
|
<template v-if="outbound.stream.isReality">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "domainName" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.serverName" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>uTLS</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="outbound.stream.reality.fingerprint"
|
||||||
|
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Short Id</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>SpiderX</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Public Key</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.publicKey" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||||
|
<a-form-item style="margin: 10px 0">
|
||||||
|
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
|
||||||
|
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
{{end}}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<td>{{ i18n "pages.inbounds.network"}}</td>
|
<td>{{ i18n "pages.inbounds.network"}}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
<a-row>
|
<tr>
|
||||||
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
</a-row>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
|
||||||
<a-input style="width: 45%" v-model.trim="account.user"
|
</tr>
|
||||||
addon-before='{{ i18n "username" }}'></a-input>
|
</table>
|
||||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||||
addon-before='{{ i18n "password" }}'>
|
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -3,100 +3,7 @@
|
|||||||
<template v-if="inbound.isSSMultiUser">
|
<template v-if="inbound.isSSMultiUser">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
{{template "form/client"}}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>password
|
|
||||||
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
|
||||||
<td>Telegram Username</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="delayedStart">
|
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
@@ -119,7 +26,7 @@
|
|||||||
<td>{{ i18n "encryption" }}</td>
|
<td>{{ i18n "encryption" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass" @change="SSMethodChange">
|
<a-select v-model="inbound.settings.method" style="width: 250px;" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -139,7 +46,7 @@
|
|||||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "password" }}</td>
|
<td style="width: 30%;">{{ i18n "password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||||
@@ -12,21 +12,23 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr v-if="inbound.settings.auth === 'password'">
|
<tr v-if="inbound.settings.auth === 'password'">
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<a-form-item>
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
<a-row>
|
<tr>
|
||||||
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
</a-row>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
|
||||||
<a-input style="width: 45%" v-model.trim="account.user"
|
</tr>
|
||||||
addon-before='{{ i18n "username" }}'></a-input>
|
</table>
|
||||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||||
addon-before='{{ i18n "password" }}'>
|
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
</a-input>
|
||||||
</template>
|
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
</a-input>
|
<template slot="addonAfter">
|
||||||
</a-input-group>
|
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
</a-form-item>
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -2,98 +2,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
{{template "form/client"}}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Password</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
|
||||||
<td>Telegram Username</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="delayedStart">
|
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
@@ -110,12 +19,12 @@
|
|||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && inbound.tls">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button type="primary" size="small"
|
<a-button type="primary" size="small"
|
||||||
@click="inbound.settings.addTrojanFallback()">
|
@click="inbound.settings.addFallback()">
|
||||||
+
|
+
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -124,27 +33,54 @@
|
|||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||||
<a-divider>
|
<a-divider style="margin:0;">
|
||||||
fallback[[ index + 1 ]]
|
fallback[[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delTrojanFallback(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="name">
|
<table width="100%">
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td style="width: 20%;">Name</td>
|
||||||
<a-form-item label="alpn">
|
<td>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-input v-model="fallback.name" style="width: 250px"></a-input>
|
||||||
<a-form-item label="path">
|
</a-form-item>
|
||||||
<a-input v-model="fallback.path"></a-input>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item label="dest">
|
<tr>
|
||||||
<a-input v-model="fallback.dest"></a-input>
|
<td>Alpn</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item label="xver">
|
<a-form-item>
|
||||||
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
<a-input v-model="fallback.alpn" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Path</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model="fallback.path" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Dest</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model="fallback.dest" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>xVer</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -2,109 +2,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
{{template "form/client"}}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="inbound.canEnableTlsFlow()">
|
|
||||||
<td>Flow</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
|
||||||
<td>Telegram Username</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="delayedStart">
|
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
@@ -123,7 +21,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && inbound.tls">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-row>
|
||||||
@@ -137,27 +35,54 @@
|
|||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||||
<a-divider>
|
<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="name">
|
<table width="100%">
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td style="width: 20%;">Name</td>
|
||||||
<a-form-item label="alpn">
|
<td>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-input v-model="fallback.name" style="width: 250px"></a-input>
|
||||||
<a-form-item label="path">
|
</a-form-item>
|
||||||
<a-input v-model="fallback.path"></a-input>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item label="dest">
|
<tr>
|
||||||
<a-input v-model="fallback.dest"></a-input>
|
<td>Alpn</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item label="xver">
|
<a-form-item>
|
||||||
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
<a-input v-model="fallback.alpn" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Path</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model="fallback.path" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Dest</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model="fallback.dest" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>xVer</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -2,98 +2,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
{{template "form/client"}}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
|
||||||
<td>Telegram Username</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="delayedStart">
|
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
|
|||||||
32
web/html/xui/form/stream/external_proxy.html
Normal file
32
web/html/xui/form/stream/external_proxy.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{{define "form/externalProxy"}}
|
||||||
|
<a-form layout="inline">
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
|
<a-form-item label="External Proxy">
|
||||||
|
<a-switch v-model="externalProxy"></a-switch>
|
||||||
|
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<table width="100%" class="ant-table-tbody" v-if="externalProxy" style="margin-bottom:5px">
|
||||||
|
<tr style="line-height: 40px;">
|
||||||
|
<td width="100%">
|
||||||
|
<a-input-group style="margin: 0 5px;" compact v-for="(row, index) in inbound.stream.externalProxy">
|
||||||
|
<template>
|
||||||
|
<a-tooltip title="Force TLS">
|
||||||
|
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
|
||||||
|
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option value="tls">TLS</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||||
|
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||||
|
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
||||||
|
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||||
|
</a-input-group>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-form>
|
||||||
|
{{end}}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<td>{{ i18n "camouflage" }}</td>
|
<td>{{ i18n "camouflage" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.quic.security" style="width: 200px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none</a-select-option>
|
<a-select-option value="none">none</a-select-option>
|
||||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="inbound.stream.quic.key" style="width: 200px;"></a-input>
|
<a-input v-model.trim="inbound.stream.quic.key" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<td>{{ i18n "camouflage" }}</td>
|
<td>{{ i18n "camouflage" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.quic.type" style="width: 200px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-divider style="margin:0;"></a-divider>
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="{{ i18n "transmission" }}">
|
<a-form-item label="{{ i18n "transmission" }}">
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
style="width: 150px;" :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">kcp</a-select-option>
|
<a-select-option value="kcp">KCP</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">http</a-select-option>
|
<a-select-option value="http">HTTP2</a-select-option>
|
||||||
<a-select-option value="quic">quic</a-select-option>
|
<a-select-option value="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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -33,8 +33,7 @@
|
|||||||
<td>T-Proxy</td>
|
<td>T-Proxy</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
|
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="off">OFF</a-select-option>
|
<a-select-option value="off">OFF</a-select-option>
|
||||||
<a-select-option value="redirect">Redirect</a-select-option>
|
<a-select-option value="redirect">Redirect</a-select-option>
|
||||||
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
||||||
|
|||||||
@@ -32,35 +32,32 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "pages.inbounds.stream.tcp.requestPath" }}</td>
|
<td style="vertical-align: top; padding-top: 10px;">{{ i18n "pages.inbounds.stream.tcp.requestPath" }}
|
||||||
|
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
|
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;">
|
||||||
|
<a-button size="small" slot="addonAfter"
|
||||||
|
@click="inbound.stream.tcp.request.removePath(index)"
|
||||||
|
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
|
||||||
|
</a-input>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2" width="100%">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
<a-form-item>
|
||||||
<a-row>
|
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||||
<a-button size="small"
|
<a-button size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
|
||||||
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
+
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
</a-button>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
</a-row>
|
</a-input>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-button slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
|
||||||
<template slot="addonAfter">
|
|
||||||
<a-button size="small"
|
|
||||||
@click="inbound.stream.tcp.request.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -92,24 +89,19 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2" width="100%">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
<a-form-item>
|
||||||
<a-row>
|
<span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
|
||||||
<a-button size="small"
|
<a-button size="small" style="margin-left: 10px"
|
||||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
|
||||||
+
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||||
</a-button>
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
</a-row>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small"
|
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
|
||||||
@click="inbound.stream.tcp.response.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -1,47 +1,24 @@
|
|||||||
{{define "form/streamWS"}}
|
{{define "form/streamWS"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<table width="100%" class="ant-table-tbody">
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
<tr v-if="inbound.canEnableTls()">
|
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||||
<td>Accept Proxy Protocol</td>
|
</a-form-item>
|
||||||
<td>
|
<br>
|
||||||
<a-form-item>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
<br>
|
||||||
</tr>
|
<a-form-item style="width: 100%;">
|
||||||
<tr>
|
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||||
<td>{{ i18n "path" }}</td>
|
<a-button size="small" style="margin-left: 10px" @click="inbound.stream.ws.addHeader()">+</a-button>
|
||||||
<td>
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
<a-form-item>
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
<a-input v-model.trim="inbound.stream.ws.path" style="width: 250px;"></a-input>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
</a-form-item>
|
</a-input>
|
||||||
</td>
|
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
</tr>
|
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
|
||||||
<tr>
|
</a-input>
|
||||||
<td colspan="2">
|
</a-input-group>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
</a-form-item>
|
||||||
<a-row>
|
|
||||||
<a-button size="small"
|
|
||||||
@click="inbound.stream.ws.addHeader('Host', '')">
|
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
|
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
|
||||||
<template slot="addonAfter">
|
|
||||||
<a-button size="small"
|
|
||||||
@click="inbound.stream.ws.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
</a-input>
|
|
||||||
</a-input-group>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,207 +1,167 @@
|
|||||||
{{define "form/tlsSettings"}}
|
{{define "form/tlsSettings"}}
|
||||||
<!-- tls enable -->
|
<!-- tls enable -->
|
||||||
<a-divider style="margin:0;"></a-divider>
|
<a-form v-if="inbound.canEnableTls()" layout="inline">
|
||||||
<a-form v-if="inbound.canSetTls()" layout="inline">
|
<a-divider style="margin:0;"></a-divider>
|
||||||
<a-form-item label="TLS">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-switch v-model="inbound.tls">
|
<tr>
|
||||||
</a-switch>
|
<td colspan="2">
|
||||||
</a-form-item>
|
<a-form-item label='{{ i18n "security" }}'>
|
||||||
<a-form-item label="Reality" v-if="inbound.canEnableReality()">
|
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||||
<a-switch v-model="inbound.reality"></a-switch>
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
</a-form-item>
|
<a-radio-button value="tls">TLS</a-radio-button>
|
||||||
</a-form>
|
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<a-form v-if="inbound.tls" layout="inline">
|
<template v-if="inbound.stream.isTls">
|
||||||
<table width="100%" class="ant-table-tbody">
|
<tr>
|
||||||
<tr>
|
<td>SNI</td>
|
||||||
<td>SNI</td>
|
<td>
|
||||||
<td>
|
<a-form-item placeholder="Server Name Indication">
|
||||||
<a-form-item placeholder="Server Name Indication" v-if="inbound.tls">
|
<a-input v-model.trim="inbound.stream.tls.sni" style="width: 250px"></a-input>
|
||||||
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>CipherSuites</td>
|
||||||
<td>CipherSuites</td>
|
<td>
|
||||||
<td>
|
<a-form-item>
|
||||||
<a-form-item>
|
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select-option value="">auto</a-select-option>
|
||||||
<a-select-option value="">auto</a-select-option>
|
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
</a-select>
|
||||||
</a-select>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>Min/Max Version</td>
|
||||||
<td>MinVersion</td>
|
<td>
|
||||||
<td>
|
<a-form-item>
|
||||||
<a-form-item>
|
<a-input-group compact>
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select style="width: 125px" v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
<a-select style="width: 125px" v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>MaxVersion</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<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-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>uTLS</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||||
|
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value=''>None</a-select-option>
|
||||||
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>ALPN</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select
|
||||||
|
mode="multiple"
|
||||||
|
style="width: 250px"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
v-model="inbound.stream.tls.alpn">
|
||||||
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Allow insecure</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Reject Unknown SNI</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "certificate" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
||||||
|
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-if="cert.useFile">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>uTLS</td>
|
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
||||||
style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value=''>None</a-select-option>
|
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Multi Domain</td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
<a-switch v-model="multiDomain"></a-switch>
|
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
<a-button v-if="multiDomain" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})">+</a-button>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="multiDomain">
|
|
||||||
<td colspan="2">
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
|
|
||||||
<a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
|
|
||||||
<a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
|
|
||||||
<template slot="addonAfter">
|
|
||||||
<a-button size="small" style="margin: 0px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
|
|
||||||
</template>
|
|
||||||
</a-input>
|
|
||||||
</a-input-group>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>{{ i18n "domainName" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ALPN</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-select
|
|
||||||
mode="multiple"
|
|
||||||
style="width: 250px"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="inbound.stream.tls.alpn">
|
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Allow insecure</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Reject Unknown SNI</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
|
||||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
|
||||||
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<template v-if="cert.useFile">
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
<tr>
|
|
||||||
<td>ocspStapling</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
</table>
|
<template v-else>
|
||||||
</a-form>
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<tr>
|
||||||
|
<td>ocspStapling</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
<a-form v-if="inbound.reality" layout="inline">
|
<template v-if="inbound.stream.isReality">
|
||||||
<table width="100%" class="ant-table-tbody">
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "domainName" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Show</td>
|
<td>Show</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -223,7 +183,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||||
style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -285,6 +245,7 @@
|
|||||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
|
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</template>
|
||||||
</table>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -2,34 +2,65 @@
|
|||||||
<template slot="actions" slot-scope="text, client, index">
|
<template slot="actions" slot-scope="text, client, index">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "qrCode" }}</template>
|
<template slot="title">{{ i18n "qrCode" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record,index,'',client.email);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "info" }}</template>
|
<template slot="title">{{ i18n "info" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="info-circle" @click="showInfo(record,index);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="retweet" @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0"></a-icon>
|
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)"
|
||||||
|
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: #3c89e8' : 'color: blue'"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template>
|
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template>
|
||||||
<a-icon style="font-size: 24px;" type="delete" v-if="isRemovable(record.id)" @click="delClient(record.id,client)"></a-icon>
|
<a-popconfirm @confirm="delClient(record.id,client,false)"
|
||||||
|
title='{{ i18n "pages.inbounds.deleteClientContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "delete"}}'
|
||||||
|
ok-type="danger"
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" style="color: #e04141"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px; cursor: pointer;" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template slot="enable" slot-scope="text, client, index">
|
<template slot="enable" slot-scope="text, client, index">
|
||||||
<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 v-if="isClientOnline(client.email)">
|
||||||
|
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-tag>{{ i18n "offline" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
<template slot="client" slot-scope="text, client">
|
<template slot="client" slot-scope="text, client">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
||||||
|
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||||
|
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||||
|
</template>
|
||||||
|
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
||||||
|
</a-badge>
|
||||||
|
</a-tooltip>
|
||||||
[[ client.email ]]
|
[[ client.email ]]
|
||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content" v-if="client.email">
|
<template slot="content" v-if="client.email">
|
||||||
<table cellpadding="2" width="100%">
|
<table cellpadding="2" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -38,24 +69,200 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.totalGB > 0">
|
<tr v-if="client.totalGB > 0">
|
||||||
<td>{{ i18n "remained" }}</td>
|
<td>{{ i18n "remained" }}</td>
|
||||||
<td>[[ sizeFormat(client.totalGB - getUpStats(record, client.email) - getDownStats(record, client.email)) ]]</td>
|
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :color="statsColor(record, client.email)">
|
<table>
|
||||||
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
|
<tr>
|
||||||
<template v-if="client.totalGB > 0">[[client._totalGB]]GB</template>
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
<template v-else>∞</template>
|
[[ sizeFormat(getSumStats(record, client.email)) ]]
|
||||||
</a-tag>
|
</td>
|
||||||
|
<td width="120px" v-if="!client.enable">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'"
|
||||||
|
:show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
|
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else class="infinite-bar">
|
||||||
|
<a-progress
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : ''"
|
||||||
|
:percent="100"></a-progress>
|
||||||
|
</td>
|
||||||
|
<td width="60px">
|
||||||
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
|
<span v-else style="font-weight: 100;font-size: 14pt;">∞</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, client, index">
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
<template v-if="client.expiryTime > 0">
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, client.expiryTime)">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
<template slot="content">
|
||||||
</a-tag>
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" class="infinite-bar">
|
||||||
|
<a-progress :show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
|
</td>
|
||||||
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">∞</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else-if="client.expiryTime < 0" color="cyan">[[ client._expiryTime ]] {{ i18n "pages.client.days" }}</a-tag>
|
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
<template slot="actionMenu" slot-scope="text, client, index">
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="ellipsis" style="font-size: 20px;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item v-if="record.hasLink()" @click="showQrcode(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="qrcode"></a-icon>
|
||||||
|
{{ i18n "qrCode" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="openEditClient(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="edit"></a-icon>
|
||||||
|
{{ i18n "pages.client.edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="showInfo(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="info-circle"></a-icon>
|
||||||
|
{{ i18n "info" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0">
|
||||||
|
<a-icon style="font-size: 14px;" type="retweet"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="isRemovable(record.id)" @click="delClient(record.id,client)">
|
||||||
|
<a-icon style="font-size: 14px;" type="delete"></a-icon>
|
||||||
|
<span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item>
|
||||||
|
<a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)">
|
||||||
|
</a-switch>
|
||||||
|
{{ i18n "enable"}}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="info" slot-scope="text, client, index">
|
||||||
|
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-if="!client.enable">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'"
|
||||||
|
:show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content" v-if="client.email">
|
||||||
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||||
|
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else class="infinite-bar">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : ''"
|
||||||
|
:percent="100"></a-progress>
|
||||||
|
</td>
|
||||||
|
<td width="80px">
|
||||||
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
|
<span v-else style="font-weight: 100;font-size: 14pt;">∞</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="text-align: center;">
|
||||||
|
<a-divider style="margin: 0; border-collapse: separate;"></a-divider>
|
||||||
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" class="infinite-bar">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-progress :show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<td colspan="3" style="text-align: center;">
|
||||||
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px; border: none;"
|
||||||
|
:color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">∞</a-tag>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-badge>
|
||||||
|
<a-icon v-if="!client.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
||||||
|
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;"><a-icon type="solution"></a-icon></a-button>
|
||||||
|
</a-badge>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
||||||
|
|||||||
@@ -3,70 +3,75 @@
|
|||||||
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:mask-closable="true"
|
:mask-closable="true"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="600px"
|
width="600px"
|
||||||
|
:class="themeSwitcher.currentTheme"
|
||||||
>
|
>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr><td>
|
<tr><td>
|
||||||
<table>
|
<table>
|
||||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
||||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag>[[ dbInbound.address ]]</a-tag></td></tr>
|
||||||
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="blue">[[ inbound.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||||
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
<tr>
|
||||||
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
<td>{{ i18n "host" }}</td>
|
||||||
|
<td v-if="inbound.host"><a-tag>[[ inbound.host ]]</a-tag></td>
|
||||||
<tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr>
|
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||||
<tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "path" }}</td>
|
||||||
|
<td v-if="inbound.path"><a-tag>[[ inbound.path ]]</a-tag></td>
|
||||||
|
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
|
||||||
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.isQuic">
|
<template v-if="inbound.isQuic">
|
||||||
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
||||||
<tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr>
|
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
|
||||||
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr>
|
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.isKcp">
|
<template v-if="inbound.isKcp">
|
||||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr>
|
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
|
||||||
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.isGrpc">
|
<template v-if="inbound.isGrpc">
|
||||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
<tr><td>grpc serviceName</td><td><a-tag>[[ inbound.serviceName ]]</a-tag></td></tr>
|
||||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
<tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||||
<td v-if="inbound.tls">
|
<td>
|
||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
{{ i18n "security" }}
|
||||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
||||||
|
<br />
|
||||||
|
<template v-if="inbound.stream.security != 'none'">
|
||||||
|
{{ i18n "domainName" }}
|
||||||
|
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td v-else-if="inbound.reality">
|
|
||||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
|
||||||
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
|
||||||
</td>
|
|
||||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "encryption" }}</td>
|
<td>{{ i18n "encryption" }}</td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.method ]]</a-tag></td>
|
||||||
</tr><tr v-if="inbound.isSS2022">
|
</tr><tr v-if="inbound.isSS2022">
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
<td><a-tag>[[ inbound.settings.password ]]</a-tag></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<template v-if="infoModal.clientSettings">
|
<template v-if="infoModal.clientSettings">
|
||||||
@@ -74,37 +79,37 @@
|
|||||||
<table style="margin-bottom: 10px;">
|
<table style="margin-bottom: 10px;">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "pages.inbounds.email" }}</td>
|
<td>{{ i18n "pages.inbounds.email" }}</td>
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings.email ]]</a-tag></td>
|
<td><a-tag color="blue">[[ infoModal.clientSettings.email ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.clientSettings.id">
|
<tr v-if="infoModal.clientSettings.id">
|
||||||
<td>ID</td>
|
<td>ID</td>
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings.id ]]</a-tag></td>
|
<td><a-tag>[[ infoModal.clientSettings.id ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
|
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
|
||||||
<td>Flow</td>
|
<td>Flow</td>
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.clientSettings.password">
|
<tr v-if="infoModal.clientSettings.password">
|
||||||
<td>Password</td>
|
<td>Password</td>
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings.password ]]</a-tag></td>
|
<td><a-tag>[[ infoModal.clientSettings.password ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "status" }}</td>
|
<td>{{ i18n "status" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
||||||
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
<a-tag v-else>{{ i18n "disabled" }}</a-tag>
|
||||||
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.clientStats">
|
<tr v-if="infoModal.clientStats">
|
||||||
<td>{{ i18n "usage" }}</td>
|
<td>{{ i18n "usage" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
||||||
<a-tag color="blue">↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px; width: 100%; text-align: center;">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "remained" }}</th>
|
<th>{{ i18n "remained" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
||||||
@@ -120,7 +125,7 @@
|
|||||||
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
|
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="purple" class="infinite-tag">∞</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
||||||
@@ -128,8 +133,8 @@
|
|||||||
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="cyan">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag>
|
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="blue">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="purple" class="infinite-tag">∞</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -147,7 +152,7 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
<a-divider>Telegram Username</a-divider>
|
<a-divider>Telegram ID</a-divider>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
<a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
||||||
<a-col :span="2">
|
<a-col :span="2">
|
||||||
@@ -162,7 +167,7 @@
|
|||||||
<template v-if="dbInbound.hasLink()">
|
<template v-if="dbInbound.hasLink()">
|
||||||
<a-divider>URL</a-divider>
|
<a-divider>URL</a-divider>
|
||||||
<a-row v-for="(link,index) in infoModal.links">
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
<a-col :span="22"><a-tag color="blue">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
<a-col :span="2" style="text-align: right;">
|
<a-col :span="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
@@ -177,7 +182,7 @@
|
|||||||
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
|
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
|
||||||
<a-divider>URL</a-divider>
|
<a-divider>URL</a-divider>
|
||||||
<a-row v-for="(link,index) in infoModal.links">
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
<a-col :span="22"><a-tag color="blue">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
<a-col :span="2" style="text-align: right;">
|
<a-col :span="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
@@ -194,9 +199,9 @@
|
|||||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||||
<th>FollowRedirect</th>
|
<th>FollowRedirect</th>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.address ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -208,7 +213,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.udp]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-if="inbound.settings.auth == 'password'">
|
<template v-if="inbound.settings.auth == 'password'">
|
||||||
@@ -217,9 +222,9 @@
|
|||||||
<td>{{ i18n "username" }}</td>
|
<td>{{ i18n "username" }}</td>
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
</tr><tr v-for="account,index in inbound.settings.accounts">
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td>[[ index ]]</td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.pass ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
@@ -229,9 +234,9 @@
|
|||||||
<th>{{ i18n "username" }}</th>
|
<th>{{ i18n "username" }}</th>
|
||||||
<th>{{ i18n "password" }}</th>
|
<th>{{ i18n "password" }}</th>
|
||||||
</tr><tr v-for="account,index in inbound.settings.accounts">
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td>[[ index ]]</td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.pass ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
@@ -241,7 +246,6 @@
|
|||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
settings: null,
|
|
||||||
clientSettings: null,
|
clientSettings: null,
|
||||||
clientStats: [],
|
clientStats: [],
|
||||||
upStats: 0,
|
upStats: 0,
|
||||||
@@ -256,27 +260,10 @@
|
|||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.settings = JSON.parse(this.inbound.settings);
|
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||||
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
|
||||||
this.isExpired = this.inbound.isExpiry(index);
|
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
||||||
remark = [this.dbInbound.remark, ( this.clientSettings ? this.clientSettings.email : '')].filter(Boolean).join('-');
|
|
||||||
address = this.dbInbound.address;
|
|
||||||
this.links = [];
|
|
||||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
|
||||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
|
||||||
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
|
||||||
this.links.push({
|
|
||||||
remark: remarkText,
|
|
||||||
link: this.inbound.genLink(domain.domain, remarkText, index)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.links.push({
|
|
||||||
remark: remark,
|
|
||||||
link: this.inbound.genLink(address, remark, index)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.clientSettings) {
|
if (this.clientSettings) {
|
||||||
if (this.clientSettings.subId) {
|
if (this.clientSettings.subId) {
|
||||||
this.subLink = this.genSubLink(this.clientSettings.subId);
|
this.subLink = this.genSubLink(this.clientSettings.subId);
|
||||||
@@ -291,8 +278,7 @@
|
|||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
return app.subSettings.subURI+subID+'?name='+subID;
|
||||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+subID });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -311,7 +297,7 @@
|
|||||||
if(infoModal.clientStats){
|
if(infoModal.clientStats){
|
||||||
return infoModal.clientStats.enable;
|
return infoModal.clientStats.enable;
|
||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return true;
|
||||||
},
|
},
|
||||||
get isEnable() {
|
get isEnable() {
|
||||||
if(infoModal.clientSettings){
|
if(infoModal.clientSettings){
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{{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" @ok="inModal.ok"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
: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>
|
||||||
@@ -43,15 +42,6 @@
|
|||||||
loading(loading) {
|
loading(loading) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
|
||||||
switch(protocol){
|
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
|
||||||
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
@@ -70,7 +60,7 @@
|
|||||||
return inModal.isEdit;
|
return inModal.isEdit;
|
||||||
},
|
},
|
||||||
get client() {
|
get client() {
|
||||||
return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0];
|
return inModal.inbound.clients[0];
|
||||||
},
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
@@ -78,26 +68,29 @@
|
|||||||
set delayedExpireDays(days){
|
set delayedExpireDays(days){
|
||||||
this.client.expiryTime = -86400000 * days;
|
this.client.expiryTime = -86400000 * days;
|
||||||
},
|
},
|
||||||
get multiDomain() {
|
get externalProxy() {
|
||||||
return this.inbound.stream.tls.settings.domains.length > 0;
|
return this.inbound.stream.externalProxy.length > 0;
|
||||||
},
|
},
|
||||||
set multiDomain(value) {
|
set externalProxy(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
inModal.inbound.stream.tls.server = "";
|
inModal.inbound.stream.externalProxy = [{
|
||||||
inModal.inbound.stream.tls.settings.domains = [{ remark: "", domain: window.location.hostname }];
|
forceTls: "same",
|
||||||
|
dest: window.location.hostname,
|
||||||
|
port: inModal.inbound.port,
|
||||||
|
remark: ""
|
||||||
|
}];
|
||||||
} else {
|
} else {
|
||||||
inModal.inbound.stream.tls.server = "";
|
inModal.inbound.stream.externalProxy = [];
|
||||||
inModal.inbound.stream.tls.settings.domains = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange() {
|
streamNetworkChange() {
|
||||||
if (!inModal.inbound.canSetTls()) {
|
if (!inModal.inbound.canEnableTls()) {
|
||||||
this.inModal.inbound.stream.security = 'none';
|
this.inModal.inbound.stream.security = 'none';
|
||||||
}
|
}
|
||||||
if (!inModal.inbound.canEnableReality()) {
|
if (!inModal.inbound.canEnableReality()) {
|
||||||
this.inModal.inbound.reality = false;
|
this.inModal.inbound.stream.isReality = false;
|
||||||
}
|
}
|
||||||
if (this.inModal.inbound.protocol == Protocols.VLESS && !inModal.inbound.canEnableTlsFlow()) {
|
if (this.inModal.inbound.protocol == Protocols.VLESS && !inModal.inbound.canEnableTlsFlow()) {
|
||||||
this.inModal.inbound.settings.vlesses.forEach(client => {
|
this.inModal.inbound.settings.vlesses.forEach(client => {
|
||||||
|
|||||||
@@ -8,20 +8,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ant-card-body {
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin: 0.5rem -2rem 0.5rem 2rem;
|
||||||
}
|
}
|
||||||
tr.hideExpandIcon .ant-table-row-expand-icon {
|
tr.hideExpandIcon .ant-table-row-expand-icon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.infinite-tag {
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 2rem;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
.infinite-bar .ant-progress-inner .ant-progress-bg {
|
||||||
|
background-color: #F2EAF1;
|
||||||
|
border: #D5BED2 solid 1px;
|
||||||
|
}
|
||||||
|
.dark .infinite-bar .ant-progress-inner .ant-progress-bg {
|
||||||
|
background-color: #3c1536;
|
||||||
|
border: #7a316f solid 1px;
|
||||||
|
}
|
||||||
|
.ant-collapse {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.online-animation .ant-badge-status-dot {
|
||||||
|
animation: 1.2s ease infinite normal none running onlineAnimation;
|
||||||
|
}
|
||||||
|
@keyframes onlineAnimation {
|
||||||
|
0%, 50%, 100% { transform: scale(1); opacity: 1; }
|
||||||
|
10% { transform: scale(1.5); opacity: .2; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
@@ -32,54 +61,74 @@
|
|||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||||
<a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalUsage" }}:
|
{{ i18n "pages.inbounds.totalUsage" }}:
|
||||||
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.inboundCount" }}:
|
{{ i18n "pages.inbounds.inboundCount" }}:
|
||||||
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
<a-tag color="blue">[[ dbInbounds.length ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
|
</a-back-top>
|
||||||
{{ i18n "clients" }}:
|
{{ i18n "clients" }}:
|
||||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
<a-tag color="blue">[[ total.clients ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in onlineClients">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag color="green" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">
|
||||||
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
|
||||||
|
</a-button>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
<a-button type="primary" icon="menu">
|
||||||
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
|
||||||
|
</a-button>
|
||||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item key="import">
|
||||||
|
<a-icon type="import"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.importInbound" }}
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item key="export">
|
<a-menu-item key="export">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export" }}
|
{{ i18n "pages.inbounds.export" }}
|
||||||
@@ -99,11 +148,11 @@
|
|||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
|
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||||
<a-select v-model="refreshInterval"
|
<a-select v-model="refreshInterval"
|
||||||
v-if="isRefreshEnabled"
|
v-if="isRefreshEnabled"
|
||||||
@change="changeRefreshInterval"
|
style="width: 70px;"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
@change="changeRefreshInterval" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
||||||
@@ -111,30 +160,36 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<a-switch v-model="enableFilter"
|
<div style="display: flex; align-items: center; justify-content: flex-start;">
|
||||||
checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
|
<a-switch v-model="enableFilter"
|
||||||
@change="toggleFilter" style="margin-right: 10px;">
|
style="margin-right: .5rem;"
|
||||||
</a-switch>
|
@change="toggleFilter">
|
||||||
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
<a-icon slot="checkedChildren" type="search"></a-icon>
|
||||||
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
|
||||||
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
</a-switch>
|
||||||
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
|
||||||
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
|
||||||
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
</a-radio-group>
|
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||||
|
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||||
|
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<a-back-top></a-back-top>
|
||||||
|
<a-table :columns="isMobile ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1200 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination="false"
|
:pagination=pagination(searchedInbounds)
|
||||||
:expand-icon-as-cell="false"
|
:expand-icon-as-cell="false"
|
||||||
:expand-row-by-click="false"
|
:expand-row-by-click="false"
|
||||||
:expand-icon-column-index="0"
|
:expand-icon-column-index="0"
|
||||||
:row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
|
:indent-size="0"
|
||||||
style="margin-top: 20px"
|
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
||||||
@change="() => getDBInbounds()">
|
style="margin-top: 10px">
|
||||||
<template slot="action" slot-scope="text, dbInbound">
|
<template slot="action" slot-scope="text, dbInbound">
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="menu"></a-icon>
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 20px; text-decoration: solid;"></a-icon>
|
||||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item key="edit">
|
<a-menu-item key="edit">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
@@ -144,7 +199,7 @@
|
|||||||
<a-icon type="qrcode"></a-icon>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser)">
|
<template v-if="dbInbound.isMultiUser()">
|
||||||
<a-menu-item key="addClient">
|
<a-menu-item key="addClient">
|
||||||
<a-icon type="user-add"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
@@ -175,6 +230,10 @@
|
|||||||
<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" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="clipboard">
|
||||||
|
<a-icon type="copy"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.copyToClipboard" }}
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item key="clone">
|
<a-menu-item key="clone">
|
||||||
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
|
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -183,42 +242,52 @@
|
|||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
</span>
|
</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="isMobile">
|
||||||
|
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
|
{{ i18n "pages.inbounds.enable" }}
|
||||||
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
<template slot="protocol" slot-scope="text, dbInbound">
|
||||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">tls</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">reality</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="clients" slot-scope="text, dbInbound">
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
<template v-if="clientCount[dbInbound.id]">
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
<a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<table cellpadding="2" width="100%">
|
<table cellpadding="2" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -231,7 +300,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :color="dbInbound.total == 0 ? 'green' : dbInbound.up + dbInbound.down < dbInbound.total ? 'cyan' : 'red'">
|
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||||
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
<template v-if="dbInbound.total > 0">
|
<template v-if="dbInbound.total > 0">
|
||||||
[[ sizeFormat(dbInbound.total) ]]
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
@@ -244,35 +313,117 @@
|
|||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||||
<template v-if="dbInbound.expiryTime > 0">
|
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<a-tag v-if="dbInbound.isExpiry" color="red">
|
<template slot="content">
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px;" :color="usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||||
|
[[ remainedDays(dbInbound._expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag v-else color="blue">
|
</a-popover>
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
<a-tag v-else color="purple" class="infinite-tag">∞</a-tag>
|
||||||
</a-tag>
|
</template>
|
||||||
</template>
|
<template slot="info" slot-scope="text, dbInbound">
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<table cellpadding="2">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.protocol" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||||
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
|
<a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||||
|
<td><a-tag>[[ dbInbound.port ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="clientCount[dbInbound.id]">
|
||||||
|
<td>{{ i18n "clients" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
|
||||||
|
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||||
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
|
<template v-if="dbInbound.total > 0">
|
||||||
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
|
</template>
|
||||||
|
<template v-else>∞</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
|
||||||
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">∞</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-badge>
|
||||||
|
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
||||||
|
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
||||||
|
<a-icon type="info"></a-icon>
|
||||||
|
</a-button>
|
||||||
|
</a-badge>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expandedRowRender" slot-scope="record">
|
<template slot="expandedRowRender" slot-scope="record">
|
||||||
<a-table
|
<a-table
|
||||||
v-if="(record.protocol === Protocols.VLESS) || (record.protocol === Protocols.VMESS)"
|
|
||||||
:row-key="client => client.id"
|
:row-key="client => client.id"
|
||||||
:columns="innerColumns"
|
:columns="isMobile ? innerMobileColumns : innerColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
:pagination="false"
|
:pagination=pagination(getInboundClients(record))
|
||||||
style="margin-left: 20px;"
|
style="margin: -12px -6px -13px;">
|
||||||
>
|
|
||||||
{{template "client_table"}}
|
|
||||||
</a-table>
|
|
||||||
<a-table
|
|
||||||
v-else-if="record.protocol === Protocols.TROJAN || record.toInbound().isSSMultiUser"
|
|
||||||
:row-key="client => client.id"
|
|
||||||
:columns="innerTrojanColumns"
|
|
||||||
:data-source="getInboundClients(record)"
|
|
||||||
:pagination="false"
|
|
||||||
style="margin-left: 20px;"
|
|
||||||
>
|
|
||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
</template>
|
</template>
|
||||||
@@ -284,6 +435,12 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||||
|
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
|
||||||
|
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||||
|
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
|
||||||
|
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
||||||
|
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
<script>
|
<script>
|
||||||
const columns = [{
|
const columns = [{
|
||||||
@@ -319,7 +476,7 @@
|
|||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "clients" }}',
|
title: '{{ i18n "clients" }}',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 40,
|
width: 50,
|
||||||
scopedSlots: { customRender: 'clients' },
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}',
|
title: '{{ i18n "pages.inbounds.traffic" }}',
|
||||||
@@ -329,26 +486,46 @@
|
|||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 80,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'expiryTime' },
|
scopedSlots: { customRender: 'expiryTime' },
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
const mobileColums = [{
|
||||||
|
title: "ID",
|
||||||
|
align: 'right',
|
||||||
|
dataIndex: "id",
|
||||||
|
width: 10,
|
||||||
|
responsive: ["s"],
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
|
align: 'center',
|
||||||
|
width: 25,
|
||||||
|
scopedSlots: { customRender: 'action' },
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||||
|
align: 'left',
|
||||||
|
width: 70,
|
||||||
|
dataIndex: "remark",
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.info" }}',
|
||||||
|
align: 'center',
|
||||||
|
width: 10,
|
||||||
|
scopedSlots: { customRender: 'info' },
|
||||||
|
}];
|
||||||
|
|
||||||
const innerColumns = [
|
const innerColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 50, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 20, scopedSlots: { customRender: 'enable' } },
|
||||||
|
{ title: '{{ i18n "online" }}', width: 20, scopedSlots: { customRender: 'online' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 60, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 55, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'UUID', width: 120, dataIndex: "id" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerMobileColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 10, align: 'center', scopedSlots: { customRender: 'actionMenu' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 90, align: 'left', scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.info" }}', width: 10, align: 'center', scopedSlots: { customRender: 'info' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 55, scopedSlots: { customRender: 'expiryTime' } },
|
|
||||||
{ title: '{{ i18n "password" }}', width: 165, dataIndex: "password" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
@@ -369,18 +546,19 @@
|
|||||||
defaultCert: '',
|
defaultCert: '',
|
||||||
defaultKey: '',
|
defaultKey: '',
|
||||||
clientCount: [],
|
clientCount: [],
|
||||||
|
onlineClients: [],
|
||||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
subSettings: {
|
subSettings: {
|
||||||
enable : false,
|
enable : false,
|
||||||
port: 0,
|
subURI : ''
|
||||||
path: '',
|
|
||||||
domain: '',
|
|
||||||
tls: false
|
|
||||||
},
|
},
|
||||||
|
remarkModel: '-ieo',
|
||||||
tgBotEnable: false,
|
tgBotEnable: false,
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
|
pageSize: 0,
|
||||||
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -393,11 +571,19 @@
|
|||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await this.getOnlineUsers();
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
|
async getOnlineUsers() {
|
||||||
|
const msg = await HttpUtil.post('/xui/inbound/onlines');
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onlineClients = msg.obj != null ? msg.obj : [];
|
||||||
|
},
|
||||||
async getDefaultSettings() {
|
async getDefaultSettings() {
|
||||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
@@ -411,11 +597,10 @@
|
|||||||
this.tgBotEnable = tgBotEnable;
|
this.tgBotEnable = tgBotEnable;
|
||||||
this.subSettings = {
|
this.subSettings = {
|
||||||
enable : subEnable,
|
enable : subEnable,
|
||||||
port: subPort,
|
subURI: subURI
|
||||||
path: subPath,
|
|
||||||
domain: subDomain,
|
|
||||||
tls: subTLS
|
|
||||||
};
|
};
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
this.remarkModel = remarkModel;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
@@ -441,8 +626,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound, inbound) {
|
getClientCounts(dbInbound, inbound) {
|
||||||
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [];
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = inbound.clients;
|
||||||
clientStats = dbInbound.clientStats
|
clientStats = dbInbound.clientStats
|
||||||
now = new Date().getTime()
|
now = new Date().getTime()
|
||||||
if (clients) {
|
if (clients) {
|
||||||
@@ -450,6 +635,7 @@
|
|||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||||
|
if(this.isClientOnline(client.email)) online.push(client.email);
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if (!client.enable) {
|
if (!client.enable) {
|
||||||
@@ -471,6 +657,7 @@
|
|||||||
deactive: deactive,
|
deactive: deactive,
|
||||||
depleted: depleted,
|
depleted: depleted,
|
||||||
expiring: expiring,
|
expiring: expiring,
|
||||||
|
online: online,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
@@ -530,6 +717,9 @@
|
|||||||
},
|
},
|
||||||
generalActions(action) {
|
generalActions(action) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
|
case "import":
|
||||||
|
this.importInbound();
|
||||||
|
break;
|
||||||
case "export":
|
case "export":
|
||||||
this.exportAllLinks();
|
this.exportAllLinks();
|
||||||
break;
|
break;
|
||||||
@@ -547,10 +737,10 @@
|
|||||||
clickAction(action, dbInbound) {
|
clickAction(action, dbInbound) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "qrcode":
|
case "qrcode":
|
||||||
this.showQrcode(dbInbound);
|
this.showQrcode(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "showInfo":
|
case "showInfo":
|
||||||
this.showInfo(dbInbound);
|
this.showInfo(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "edit":
|
case "edit":
|
||||||
this.openEditInbound(dbInbound.id);
|
this.openEditInbound(dbInbound.id);
|
||||||
@@ -564,6 +754,9 @@
|
|||||||
case "export":
|
case "export":
|
||||||
this.inboundLinks(dbInbound.id);
|
this.inboundLinks(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
case "clipboard":
|
||||||
|
this.copyToClipboard(dbInbound.id);
|
||||||
|
break;
|
||||||
case "resetTraffic":
|
case "resetTraffic":
|
||||||
this.resetTraffic(dbInbound.id);
|
this.resetTraffic(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
@@ -615,7 +808,8 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
||||||
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||||
okText: '{{ i18n "pages.inbounds.update"}}',
|
okText: '{{ i18n "pages.inbounds.clone"}}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
const baseInbound = dbInbound.toInbound();
|
const baseInbound = dbInbound.toInbound();
|
||||||
@@ -750,9 +944,9 @@
|
|||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' #' + dbInboundId,
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
@@ -765,33 +959,28 @@
|
|||||||
},
|
},
|
||||||
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.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delClient(dbInboundId, client) {
|
delClient(dbInboundId, client,confirmation = true) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
clientId = this.getClientId(dbInbound.protocol, client);
|
clientId = this.getClientId(dbInbound.protocol, client);
|
||||||
this.$confirm({
|
if (confirmation){
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
this.$confirm({
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
|
||||||
class: themeSwitcher.darkCardClass,
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
okText: '{{ i18n "delete"}}',
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
});
|
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||||
},
|
});
|
||||||
getClients(protocol, clientSettings) {
|
} else {
|
||||||
switch (protocol) {
|
this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`);
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
|
||||||
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
|
||||||
default: return null;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getClientId(protocol, client) {
|
getClientId(protocol, client) {
|
||||||
@@ -805,7 +994,7 @@
|
|||||||
newDbInbound = new DBInbound(dbInbound);
|
newDbInbound = new DBInbound(dbInbound);
|
||||||
if (dbInbound.listen.startsWith("@")){
|
if (dbInbound.listen.startsWith("@")){
|
||||||
rootInbound = this.inbounds.find((i) =>
|
rootInbound = this.inbounds.find((i) =>
|
||||||
i.tls &&
|
i.isTcp &&
|
||||||
['trojan','vless'].includes(i.protocol) &&
|
['trojan','vless'].includes(i.protocol) &&
|
||||||
i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
|
i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
|
||||||
);
|
);
|
||||||
@@ -813,18 +1002,27 @@
|
|||||||
newDbInbound.listen = rootInbound.listen;
|
newDbInbound.listen = rootInbound.listen;
|
||||||
newDbInbound.port = rootInbound.port;
|
newDbInbound.port = rootInbound.port;
|
||||||
newInbound = newDbInbound.toInbound();
|
newInbound = newDbInbound.toInbound();
|
||||||
newInbound.stream.security = 'tls';
|
newInbound.stream.security = rootInbound.stream.security;
|
||||||
newInbound.stream.tls = rootInbound.stream.tls;
|
newInbound.stream.tls = rootInbound.stream.tls;
|
||||||
|
newInbound.stream.externalProxy = rootInbound.stream.externalProxy;
|
||||||
newDbInbound.streamSettings = newInbound.stream.toString();
|
newDbInbound.streamSettings = newInbound.stream.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newDbInbound;
|
return newDbInbound;
|
||||||
},
|
},
|
||||||
showQrcode(dbInbound, clientIndex) {
|
showQrcode(dbInboundId, client) {
|
||||||
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, clientIndex);
|
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
|
||||||
},
|
},
|
||||||
showInfo(dbInbound, index) {
|
showInfo(dbInboundId, client) {
|
||||||
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
index=0;
|
||||||
|
if (dbInbound.isMultiUser()){
|
||||||
|
inbound = dbInbound.toInbound();
|
||||||
|
clients = inbound.clients;
|
||||||
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
|
}
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
infoModal.show(newDbInbound, index);
|
infoModal.show(newDbInbound, index);
|
||||||
},
|
},
|
||||||
@@ -836,7 +1034,7 @@
|
|||||||
this.loading()
|
this.loading()
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
inbound = dbInbound.toInbound();
|
inbound = dbInbound.toInbound();
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = inbound.clients;
|
||||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||||
@@ -850,31 +1048,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getInboundClients(dbInbound) {
|
getInboundClients(dbInbound) {
|
||||||
if (dbInbound.protocol == Protocols.VLESS) {
|
return dbInbound.toInbound().clients;
|
||||||
return dbInbound.toInbound().settings.vlesses;
|
|
||||||
} else if (dbInbound.protocol == Protocols.VMESS) {
|
|
||||||
return dbInbound.toInbound().settings.vmesses;
|
|
||||||
} else if (dbInbound.protocol == Protocols.TROJAN) {
|
|
||||||
return dbInbound.toInbound().settings.trojans;
|
|
||||||
} else if (dbInbound.protocol == Protocols.SHADOWSOCKS) {
|
|
||||||
return dbInbound.toInbound().settings.shadowsockses;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
resetClientTraffic(client, dbInboundId) {
|
resetClientTraffic(client, dbInboundId, confirmation = true) {
|
||||||
this.$confirm({
|
if (confirmation){
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
this.$confirm({
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + client.email,
|
||||||
class: themeSwitcher.darkCardClass,
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
okText: '{{ i18n "reset"}}',
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
})
|
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resetAllTraffic() {
|
resetAllTraffic() {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
|
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
|
||||||
@@ -884,7 +1078,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||||
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
@@ -894,48 +1088,117 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(index)
|
return dbInbound.toInbound().isExpiry(index);
|
||||||
},
|
},
|
||||||
getUpStats(dbInbound, email) {
|
getUpStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return clientStats ? clientStats.up : 0
|
return clientStats ? clientStats.up : 0;
|
||||||
},
|
},
|
||||||
getDownStats(dbInbound, email) {
|
getDownStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
|
||||||
return clientStats ? clientStats.down : 0
|
|
||||||
},
|
|
||||||
statsColor(dbInbound, email) {
|
|
||||||
if (email.length == 0) return 'blue';
|
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
return clientStats ? clientStats.down : 0;
|
||||||
|
},
|
||||||
|
getSumStats(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 0;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
return clientStats ? clientStats.up + clientStats.down : 0;
|
||||||
|
},
|
||||||
|
getRemStats(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 0;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return 0;
|
||||||
|
remained = clientStats.total - (clientStats.up + clientStats.down);
|
||||||
|
return remained>0 ? remained : 0;
|
||||||
|
},
|
||||||
|
clientStatsColor(dbInbound, email) {
|
||||||
|
if (email.length == 0) return clientUsageColor();
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
return clientUsageColor(clientStats, app.trafficDiff)
|
||||||
|
},
|
||||||
|
statsProgress(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 100;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return 0;
|
||||||
|
if (clientStats.total == 0) return 100;
|
||||||
|
return 100*(clientStats.down + clientStats.up)/clientStats.total;
|
||||||
|
},
|
||||||
|
expireProgress(expTime, reset) {
|
||||||
|
now = new Date().getTime();
|
||||||
|
remainedSeconds = expTime < 0 ? -expTime/1000 : (expTime-now)/1000;
|
||||||
|
resetSeconds = reset * 86400;
|
||||||
|
if (remainedSeconds >= resetSeconds) return 0;
|
||||||
|
return 100*(1-(remainedSeconds/resetSeconds));
|
||||||
|
},
|
||||||
|
remainedDays(expTime){
|
||||||
|
if (expTime == 0) return null;
|
||||||
|
if (expTime < 0) return formatSecond(expTime/-1000);
|
||||||
|
now = new Date().getTime();
|
||||||
|
if (expTime < now) return '{{ i18n "depleted" }}';
|
||||||
|
return formatSecond((expTime-now)/1000);
|
||||||
|
},
|
||||||
|
statsExpColor(dbInbound, email){
|
||||||
|
if (email.length == 0) return '#7a316f';
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return '#7a316f';
|
||||||
|
statsColor = usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
||||||
|
expColor = usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
|
||||||
|
switch (true) {
|
||||||
|
case statsColor == "red" || expColor == "red":
|
||||||
|
return "#E04141";
|
||||||
|
case statsColor == "orange" || expColor == "orange":
|
||||||
|
return "#FFA031";
|
||||||
|
case statsColor == "blue" || expColor == "blue":
|
||||||
|
return "#0e49b5";
|
||||||
|
default:
|
||||||
|
return "#7a316f";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
isClientEnabled(dbInbound, email) {
|
isClientEnabled(dbInbound, email) {
|
||||||
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
|
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null;
|
||||||
return clientStats ? clientStats['enable'] : true
|
return clientStats ? clientStats['enable'] : true;
|
||||||
},
|
},
|
||||||
isRemovable(dbInbound_id) {
|
isClientOnline(email) {
|
||||||
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1
|
return this.onlineClients.includes(email);
|
||||||
|
},
|
||||||
|
isRemovable(dbInboundId) {
|
||||||
|
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1;
|
||||||
},
|
},
|
||||||
inboundLinks(dbInboundId) {
|
inboundLinks(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
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(this.remarkModel), newDbInbound.remark);
|
||||||
|
},
|
||||||
|
importInbound() {
|
||||||
|
promptModal.open({
|
||||||
|
title: '{{ i18n "pages.inbounds.importInbound" }}',
|
||||||
|
type: 'textarea',
|
||||||
|
value: '',
|
||||||
|
okText: '{{ i18n "pages.inbounds.import" }}',
|
||||||
|
confirm: async (dbInboundText) => {
|
||||||
|
await this.submit('/xui/inbound/import', {data: dbInboundText}, promptModal);
|
||||||
|
promptModal.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
exportAllLinks() {
|
exportAllLinks() {
|
||||||
let copyText = '';
|
let copyText = [];
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
copyText += dbInbound.genInboundLinks
|
copyText.push(dbInbound.genInboundLinks(this.remarkModel));
|
||||||
}
|
}
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
|
||||||
|
},
|
||||||
|
copyToClipboard(dbInboundId) {
|
||||||
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
|
||||||
},
|
},
|
||||||
async startDataRefreshLoop() {
|
async startDataRefreshLoop() {
|
||||||
while (this.isRefreshEnabled) {
|
while (this.isRefreshEnabled) {
|
||||||
@@ -963,6 +1226,30 @@
|
|||||||
this.spinning = false;
|
this.spinning = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pagination(obj){
|
||||||
|
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
||||||
|
// Set page options based on object size
|
||||||
|
sizeOptions = [];
|
||||||
|
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
||||||
|
sizeOptions.push(i.toString());
|
||||||
|
}
|
||||||
|
// Add option to see all in one page
|
||||||
|
sizeOptions.push(i.toString());
|
||||||
|
|
||||||
|
p = {
|
||||||
|
showSizeChanger: true,
|
||||||
|
size: 'small',
|
||||||
|
position: 'bottom',
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
pageSizeOptions: sizeOptions
|
||||||
|
};
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
onResize() {
|
||||||
|
this.isMobile = window.innerWidth <= 768;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchKey: debounce(function (newVal) {
|
searchKey: debounce(function (newVal) {
|
||||||
@@ -973,6 +1260,8 @@
|
|||||||
if (window.location.protocol !== "https:") {
|
if (window.location.protocol !== "https:") {
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
}
|
||||||
|
window.addEventListener('resize', this.onResize);
|
||||||
|
this.onResize();
|
||||||
this.loading();
|
this.loading();
|
||||||
this.getDefaultSettings();
|
this.getDefaultSettings();
|
||||||
if (this.isRefreshEnabled) {
|
if (this.isRefreshEnabled) {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
.ant-layout-content {
|
.ant-layout-content {
|
||||||
margin: 24px 16px;
|
margin: 24px 16px;
|
||||||
}
|
}
|
||||||
|
.ant-card-hoverable {
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
@@ -17,9 +20,9 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<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>
|
<transition name="list" appear>
|
||||||
@@ -33,21 +36,19 @@
|
|||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-row>
|
<a-row>
|
||||||
<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.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU ([[ status.cpuCount ]]core)</div>
|
<div>CPU: ([[ status.cpuCount ]]core)</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"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
: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) ]]
|
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
@@ -60,16 +61,14 @@
|
|||||||
<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.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
Swap: [[ 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">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
: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) ]]
|
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
@@ -84,64 +83,70 @@
|
|||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
x-ui: <a href="https://github.com/alireza0/x-ui/releases" target="_blank"><a-tag color="green">{{ .cur_ver }}</a-tag></a>
|
X-UI: <a href="https://github.com/alireza0/x-ui/releases" target="_blank"><a-tag color="blue">{{ .cur_ver }}</a-tag></a>
|
||||||
xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
Xray:
|
||||||
|
<a-tooltip title='{{ i18n "pages.index.xraySwitch" }}'>
|
||||||
|
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||||
|
</a-tooltip>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
xray
|
Xray
|
||||||
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag color="blue">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
os
|
OS
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.operationHoursDesc" }}
|
{{ i18n "pages.index.operationHoursDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="blue">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
{{ i18n "pages.index.xrayStatus" }}:
|
||||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||||
<a-tooltip v-if="status.xray.state === State.Error">
|
<a-popover v-if="status.xray.state === State.Error"
|
||||||
<template slot="title">
|
:overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
<span slot="title" style="font-size: 12pt">Error in running xray-core
|
||||||
|
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
|
</span>
|
||||||
|
<template slot="content">
|
||||||
|
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-popover>
|
||||||
<a-tag color="blue" 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="blue" 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="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "menu.link" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</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" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "usage"}}:
|
{{ i18n "usage"}}:
|
||||||
Memory [[ sizeFormat(status.appStats.mem) ]] -
|
Memory: [[ sizeFormat(status.appStats.mem) ]] -
|
||||||
Threads [[ status.appStats.threads ]]
|
Threads: [[ status.appStats.threads ]]
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
Host: [[ status.hostInfo.hostname ]] -
|
Host: [[ status.hostInfo.hostname ]] -
|
||||||
<template v-if="status.hostInfo.ipv4">IPv4:
|
<template v-if="status.hostInfo.ipv4">IPv4:
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -162,18 +167,31 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.connectionCount" }}: tcp: [[ status.tcpCount ]] udp: [[ status.udpCount ]]
|
<a-row>
|
||||||
<a-tooltip>
|
<a-col :span="12">
|
||||||
<template slot="title">
|
TCP: [[ status.tcpCount ]]
|
||||||
{{ i18n "pages.index.connectionCountDesc" }}
|
<a-tooltip>
|
||||||
</template>
|
<template slot="title">
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||||
</a-tooltip>
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
UDP: [[ status.udpCount ]]
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
@@ -199,7 +217,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
@@ -231,29 +249,28 @@
|
|||||||
|
|
||||||
<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" @ok="() => versionModal.visible = false"
|
:closable="true" @ok="() => versionModal.visible = false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
footer="">
|
footer="">
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
||||||
<template v-for="version, index in versionModal.versions">
|
<template v-for="version, index in versionModal.versions">
|
||||||
<a-tag :color="index % 2 == 0 ? 'blue' : 'green'"
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'blue'"
|
||||||
style="margin: 10px" @click="switchV2rayVersion(version)">
|
style="margin: 10px" @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="X-UI logs"
|
<a-modal id="log-modal" v-model="logModal.visible" title="Logs"
|
||||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="800px"
|
width="800px"
|
||||||
footer="">
|
footer="">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Count">
|
<a-form-item label="Count">
|
||||||
<a-select v-model="logModal.rows"
|
<a-select v-model="logModal.rows"
|
||||||
style="width: 80px"
|
style="width: 80px"
|
||||||
@change="openLogs()"
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
@@ -263,8 +280,7 @@
|
|||||||
<a-form-item label="Log Level">
|
<a-form-item label="Log Level">
|
||||||
<a-select v-model="logModal.level"
|
<a-select v-model="logModal.level"
|
||||||
style="width: 120px"
|
style="width: 120px"
|
||||||
@change="openLogs()"
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
<a-select-option value="info">Info</a-select-option>
|
<a-select-option value="info">Info</a-select-option>
|
||||||
<a-select-option value="warning">Warning</a-select-option>
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
@@ -275,7 +291,7 @@
|
|||||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<button class="ant-btn ant-btn-primary" @click="openLogs()"><a-icon type="sync"></a-icon> Reload</button>
|
<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-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" style="margin-bottom: 10px;"
|
<a-button type="primary" style="margin-bottom: 10px;"
|
||||||
@@ -284,17 +300,17 @@
|
|||||||
</a-button>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-input type="textarea" v-model="logModal.logs" disabled="true"
|
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.logs"></div>
|
||||||
:autosize="{ minRows: 10, maxRows: 22}"></a-input>
|
|
||||||
</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.darkCardClass"
|
:closable="true"
|
||||||
|
:class="themeSwitcher.currentTheme"
|
||||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
||||||
<p style="color: inherit; font-size: 16px; padding: 4px 2px;">
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
:message="backupModal.description"
|
||||||
[[ backupModal.description ]]
|
show-icon
|
||||||
</p>
|
></a-alert>
|
||||||
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
||||||
<a-button type="primary" @click="exportDatabase()">
|
<a-button type="primary" @click="exportDatabase()">
|
||||||
[[ backupModal.exportText ]]
|
[[ backupModal.exportText ]]
|
||||||
@@ -307,10 +323,10 @@
|
|||||||
|
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "textModal"}}
|
{{template "textModal"}}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
Running: "running",
|
Running: "running",
|
||||||
Stop: "stop",
|
Stop: "stop",
|
||||||
@@ -335,11 +351,11 @@
|
|||||||
get color() {
|
get color() {
|
||||||
const percent = this.percent;
|
const percent = this.percent;
|
||||||
if (percent < 80) {
|
if (percent < 80) {
|
||||||
return '#67C23A';
|
return '#0e49b5';
|
||||||
} else if (percent < 90) {
|
} else if (percent < 90) {
|
||||||
return '#E6A23C';
|
return '#ffa031';
|
||||||
} else {
|
} else {
|
||||||
return '#F56C6C';
|
return '#e04141';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +398,7 @@
|
|||||||
this.xray = data.xray;
|
this.xray = data.xray;
|
||||||
switch (this.xray.state) {
|
switch (this.xray.state) {
|
||||||
case State.Running:
|
case State.Running:
|
||||||
this.xray.color = "green";
|
this.xray.color = "blue";
|
||||||
break;
|
break;
|
||||||
case State.Stop:
|
case State.Stop:
|
||||||
this.xray.color = "orange";
|
this.xray.color = "orange";
|
||||||
@@ -414,9 +430,46 @@
|
|||||||
rows: 20,
|
rows: 20,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
syslog: false,
|
syslog: false,
|
||||||
|
loading: false,
|
||||||
show(logs) {
|
show(logs) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.logs = logs? logs.join("\n"): "No Record...";
|
this.logs = logs? this.formatLogs(logs) : "No Record...";
|
||||||
|
},
|
||||||
|
formatLogs(logs) {
|
||||||
|
let formattedLogs = '';
|
||||||
|
const levels = ["DEBUG","INFO","WARNING","ERROR"];
|
||||||
|
const levelColors = ["#3c89e8","#008771","#f37b24","#e04141","#bcbcbc"];
|
||||||
|
|
||||||
|
logs.forEach((log, index) => {
|
||||||
|
let [data, message] = log.split(" - ",2);
|
||||||
|
const parts = data.split(" ")
|
||||||
|
if(index>0) formattedLogs += '<br>';
|
||||||
|
|
||||||
|
if (parts.length === 3) {
|
||||||
|
const d = parts[0];
|
||||||
|
const t = parts[1];
|
||||||
|
const level = parts[2];
|
||||||
|
const levelIndex = levels.indexOf(level,levels) || 4;
|
||||||
|
|
||||||
|
//formattedLogs += `<span style="color: gray;">${index + 1}.</span>`;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[0]};">${d} ${t}</span> `;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${level}</span>`;
|
||||||
|
} else {
|
||||||
|
const levelIndex = levels.indexOf(data,levels) || 4;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${data}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message){
|
||||||
|
if(message.startsWith("XRAY:"))
|
||||||
|
message = "<b>XRAY: </b>" + message.substring(5);
|
||||||
|
else
|
||||||
|
message = "<b>X-UI: </b>" + message;
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedLogs += message ? ' - ' + message : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return formattedLogs;
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
@@ -487,8 +540,8 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
||||||
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "confirm"}}',
|
okText: '{{ i18n "confirm"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
@@ -515,13 +568,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async openLogs(){
|
async openLogs(){
|
||||||
this.loading(true);
|
logModal.loading = true;
|
||||||
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
||||||
this.loading(false);
|
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logModal.show(msg.obj);
|
logModal.show(msg.obj);
|
||||||
|
await PromiseUtil.sleep(500);
|
||||||
|
logModal.loading = false;
|
||||||
},
|
},
|
||||||
async openConfig() {
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
@@ -593,4 +647,4 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -8,8 +8,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
@media (max-width: 768px) {
|
||||||
margin-top: 10px;
|
.ant-tabs-nav .ant-tabs-tab {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-bar {
|
.ant-tabs-bar {
|
||||||
@@ -20,19 +23,6 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-msg {
|
|
||||||
color: inherit;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 20px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-msg > i {
|
|
||||||
color: inherit;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-title {
|
.collapse-title {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -47,11 +37,11 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
@@ -62,19 +52,53 @@
|
|||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-space direction="horizontal">
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<a-row>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
||||||
</a-space>
|
<a-space direction="horizontal">
|
||||||
<a-tabs default-active-key="1" :class="themeSwitcher.darkCardClass">
|
<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-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="16">
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
|
</a-back-top>
|
||||||
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
<a-tabs default-active-key="1">
|
||||||
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelConfig"}}'>
|
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelConfig"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-list item-layout="horizontal">
|
||||||
<h2 class="alert-msg">
|
<a-list-item>
|
||||||
<a-icon type="warning"></a-icon>
|
<a-row style="padding: 20px">
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
<a-col :lg="24" :xl="12">
|
||||||
</h2>
|
<a-list-item-meta title='{{ i18n "pages.settings.remarkModel"}}'>
|
||||||
</a-row>
|
<template slot="description">{{ i18n "pages.settings.sampleRemark"}}: <i>#[[ remarkSample ]]</i></template>
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
</a-list-item-meta>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-input-group style="width: 100%;">
|
||||||
|
<a-select style="padding-right: .5rem; min-width: 80%; width: auto;"
|
||||||
|
mode="multiple"
|
||||||
|
v-model="remarkModel"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="(value, key) in remarkModels" :value="key">[[ value ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select style="width: 20%;" v-model="remarkSeparator" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in remarkSeparators" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-input-group>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</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.number="allSetting.webPort"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
|
||||||
@@ -82,6 +106,7 @@
|
|||||||
<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>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.sessionMaxAge" }}' desc='{{ i18n "pages.settings.sessionMaxAgeDesc" }}' v-model="allSetting.sessionMaxAge" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.sessionMaxAge" }}' desc='{{ i18n "pages.settings.sessionMaxAgeDesc" }}' v-model="allSetting.sessionMaxAge" :min="0"></setting-list-item>
|
||||||
|
<setting-list-item type="number" title='{{ i18n "pages.settings.pageSize" }}' desc='{{ i18n "pages.settings.pageSizeDesc" }}' v-model="allSetting.pageSize" :min="0" :step="5"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.expireTimeDiff" }}' desc='{{ i18n "pages.settings.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.expireTimeDiff" }}' desc='{{ i18n "pages.settings.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.trafficDiff" }}' desc='{{ i18n "pages.settings.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.trafficDiff" }}' desc='{{ i18n "pages.settings.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.timeZone"}}' desc='{{ i18n "pages.settings.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.timeZone"}}' desc='{{ i18n "pages.settings.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
|
||||||
@@ -90,16 +115,13 @@
|
|||||||
<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
|
||||||
ref="selectLang"
|
ref="selectLang"
|
||||||
v-model="lang"
|
v-model="lang"
|
||||||
@change="setLang(lang)"
|
@change="setLang(lang)"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
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>
|
||||||
@@ -112,190 +134,46 @@
|
|||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab='{{ i18n "pages.settings.userSettings"}}'>
|
<a-tab-pane key="2" tab='{{ i18n "pages.settings.userSettings"}}'>
|
||||||
<a-form :style="'padding: 20px;' + themeSwitcher.textStyle">
|
<a-form style="padding: 20px;" layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
<table cellpadding="2">
|
||||||
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td>{{ i18n "pages.settings.oldUsername"}}:</td>
|
||||||
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
|
<td>
|
||||||
<password-input v-model="user.oldPassword" style="max-width: 300px"></password-input>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-input v-model="user.oldUsername" style="width: 200px"></a-input>
|
||||||
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
|
</a-form-item>
|
||||||
<a-input v-model="user.newUsername" style="max-width: 300px"></a-input>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
|
<tr>
|
||||||
<password-input v-model="user.newPassword" style="max-width: 300px"></password-input>
|
<td>{{ i18n "pages.settings.currentPassword"}}:</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
<password-input v-model="user.oldPassword" style="width: 200px"></password-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.settings.newUsername"}}:</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model="user.newUsername" style="width: 200px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.settings.newPassword"}}:</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<password-input v-model="user.newPassword" style="width: 200px"></password-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||||
</a-form>
|
</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.xrayConfiguration"}}'>
|
<a-list item-layout="horizontal">
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<a-tabs class="ant-card-dark-box-nohover" default-active-key="tpl-1" :class="themeSwitcher.darkCardClass" style="padding: 20px 20px;">
|
|
||||||
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.settings.templates.basicTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.generalConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.generalConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list-item>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta
|
|
||||||
title='{{ i18n "pages.settings.templates.xrayConfigFreedomStrategy" }}'
|
|
||||||
description='{{ i18n "pages.settings.templates.xrayConfigFreedomStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
v-model="freedomStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta
|
|
||||||
title='{{ i18n "pages.settings.templates.xrayConfigRoutingStrategy" }}'
|
|
||||||
description='{{ i18n "pages.settings.templates.xrayConfigRoutingStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
v-model="routingStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.blockConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigTorrent"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigAds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigFamily"}}' desc='{{ i18n "pages.settings.templates.xrayConfigFamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockCountryConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.blockCountryConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.directCountryConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.directCountryConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.ipv4Configs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.ipv4ConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
|
||||||
<a-space direction="horizontal" style="padding: 0 20px">
|
|
||||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.manualLists"}}' style="padding-top: 20px;">
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedIPs"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualBlockedIPs"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedDomains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualBlockedDomains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectIPs"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualDirectIPs"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectDomains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualDirectDomains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualIPv4Domains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualIPv4Domains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
|
||||||
</a-tab-pane>
|
|
||||||
</a-tabs>
|
|
||||||
</a-list>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<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>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
||||||
@@ -314,9 +192,8 @@
|
|||||||
<a-select
|
<a-select
|
||||||
ref="selectBotLang"
|
ref="selectBotLang"
|
||||||
v-model="allSetting.tgLang"
|
v-model="allSetting.tgLang"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<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>
|
||||||
@@ -328,14 +205,8 @@
|
|||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }}'>
|
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-list item-layout="horizontal">
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></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="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||||
@@ -345,6 +216,7 @@
|
|||||||
<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="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"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@@ -355,6 +227,7 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
<script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script>
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "component/password" .}}
|
{{template "component/password" .}}
|
||||||
{{template "component/setting"}}
|
{{template "component/setting"}}
|
||||||
@@ -373,60 +246,29 @@
|
|||||||
user: {},
|
user: {},
|
||||||
lang: getLang(),
|
lang: getLang(),
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
ipv4Settings: {
|
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
||||||
tag: "IPv4",
|
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
||||||
protocol: "freedom",
|
remarkSample: '',
|
||||||
settings: {
|
get remarkModel() {
|
||||||
domainStrategy: "UseIPv4"
|
rm = this.allSetting.remarkModel;
|
||||||
}
|
return rm.length>1 ? rm.substring(1).split('') : [];
|
||||||
},
|
},
|
||||||
directSettings: {
|
set remarkModel(value) {
|
||||||
tag: "direct",
|
rs = this.allSetting.remarkModel[0];
|
||||||
protocol: "freedom"
|
this.allSetting.remarkModel = rs + value.join('');
|
||||||
|
this.changeRemarkSample();
|
||||||
},
|
},
|
||||||
outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
|
get remarkSeparator() {
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
return this.allSetting.remarkModel.length > 1 ? this.allSetting.remarkModel.charAt(0) : '-';
|
||||||
settingsData: {
|
},
|
||||||
protocols: {
|
set remarkSeparator(value) {
|
||||||
bittorrent: ["bittorrent"],
|
this.allSetting.remarkModel = value + this.allSetting.remarkModel.substring(1);
|
||||||
},
|
this.changeRemarkSample();
|
||||||
ips: {
|
},
|
||||||
local: ["geoip:private"],
|
changeRemarkSample(){
|
||||||
cn: ["geoip:cn"],
|
sample = []
|
||||||
ir: ["geoip:ir"],
|
this.remarkModel.forEach(r => sample.push(this.remarkModels[r]));
|
||||||
ru: ["geoip:ru"],
|
this.remarkSample = sample.length == 0 ? '' : sample.join(this.remarkSeparator);
|
||||||
},
|
|
||||||
domains: {
|
|
||||||
ads: [
|
|
||||||
"geosite:category-ads-all",
|
|
||||||
"ext:iran.dat:ads"
|
|
||||||
],
|
|
||||||
google: ["geosite:google"],
|
|
||||||
netflix: ["geosite:netflix"],
|
|
||||||
cn: [
|
|
||||||
"geosite:cn",
|
|
||||||
"regexp:.*\\.cn$"
|
|
||||||
],
|
|
||||||
ru: [
|
|
||||||
"geosite:category-gov-ru",
|
|
||||||
"regexp:.*\\.ru$"
|
|
||||||
],
|
|
||||||
ir: [
|
|
||||||
"regexp:.*\\.ir$",
|
|
||||||
"ext:iran.dat:ir",
|
|
||||||
"ext:iran.dat:other",
|
|
||||||
"geosite:category-ir"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
familyProtectDNS: {
|
|
||||||
"servers": [
|
|
||||||
"1.1.1.3",
|
|
||||||
"1.0.0.3",
|
|
||||||
"94.140.14.15",
|
|
||||||
"94.140.15.16"
|
|
||||||
],
|
|
||||||
"queryStrategy": "UseIPv4"
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -440,6 +282,7 @@
|
|||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.oldAllSetting = new AllSetting(msg.obj);
|
this.oldAllSetting = new AllSetting(msg.obj);
|
||||||
this.allSetting = new AllSetting(msg.obj);
|
this.allSetting = new AllSetting(msg.obj);
|
||||||
|
app.changeRemarkSample();
|
||||||
this.saveBtnDisable = true;
|
this.saveBtnDisable = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -465,6 +308,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.settings.restartPanel" }}',
|
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||||
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "sure" }}',
|
okText: '{{ i18n "sure" }}',
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => resolve(),
|
onOk: () => resolve(),
|
||||||
@@ -476,88 +320,13 @@
|
|||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
await PromiseUtil.sleep(5000);
|
await PromiseUtil.sleep(5000);
|
||||||
const { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
||||||
|
if (host == this.oldAllSetting.webDomain) host = null;
|
||||||
|
if (port == this.oldAllSetting.webPort) port = null;
|
||||||
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||||
const url = buildURL({ host, port, isTLS, base, path: "xui/settings" });
|
const url = buildURL({ host, port, isTLS, base, path: "xui/settings" });
|
||||||
window.location.replace(url);
|
window.location.replace(url);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
async resetXrayConfigToDefault() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
|
||||||
this.saveBtnDisable = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
syncRulesWithOutbound(tag, setting) {
|
|
||||||
const newTemplateSettings = this.templateSettings;
|
|
||||||
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
|
|
||||||
const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
|
|
||||||
if (!haveRules && outboundIndex > 0) {
|
|
||||||
newTemplateSettings.outbounds.splice(outboundIndex);
|
|
||||||
}
|
|
||||||
if (haveRules && outboundIndex < 0) {
|
|
||||||
newTemplateSettings.outbounds.push(setting);
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
templateRuleGetter(routeSettings) {
|
|
||||||
const { property, outboundTag } = routeSettings;
|
|
||||||
let result = [];
|
|
||||||
if (this.templateSettings != null) {
|
|
||||||
this.templateSettings.routing.rules.forEach(
|
|
||||||
(routingRule) => {
|
|
||||||
if (
|
|
||||||
routingRule.hasOwnProperty(property) &&
|
|
||||||
routingRule.hasOwnProperty("outboundTag") &&
|
|
||||||
routingRule.outboundTag === outboundTag
|
|
||||||
) {
|
|
||||||
result.push(...routingRule[property]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
templateRuleSetter(routeSettings) {
|
|
||||||
const { data, property, outboundTag } = routeSettings;
|
|
||||||
const oldTemplateSettings = this.templateSettings;
|
|
||||||
const newTemplateSettings = oldTemplateSettings;
|
|
||||||
currentProperty = this.templateRuleGetter({ outboundTag, property })
|
|
||||||
if (currentProperty.length == 0) {
|
|
||||||
const propertyRule = {
|
|
||||||
type: "field",
|
|
||||||
outboundTag,
|
|
||||||
[property]: data
|
|
||||||
};
|
|
||||||
newTemplateSettings.routing.rules.push(propertyRule);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const newRules = [];
|
|
||||||
insertedOnce = false;
|
|
||||||
newTemplateSettings.routing.rules.forEach(
|
|
||||||
(routingRule) => {
|
|
||||||
if (
|
|
||||||
routingRule.hasOwnProperty(property) &&
|
|
||||||
routingRule.hasOwnProperty("outboundTag") &&
|
|
||||||
routingRule.outboundTag === outboundTag
|
|
||||||
) {
|
|
||||||
if (!insertedOnce && data.length > 0) {
|
|
||||||
insertedOnce = true;
|
|
||||||
routingRule[property] = data;
|
|
||||||
newRules.push(routingRule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newRules.push(routingRule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
newTemplateSettings.routing.rules = newRules;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -569,357 +338,7 @@
|
|||||||
await PromiseUtil.sleep(1000);
|
await PromiseUtil.sleep(1000);
|
||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
computed: {
|
|
||||||
templateSettings: {
|
|
||||||
get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; },
|
|
||||||
set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2); },
|
|
||||||
},
|
|
||||||
inboundSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.inbounds = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
outboundSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.outbounds = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
routingRuleSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.routing.rules = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
freedomStrategy: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings) return "AsIs";
|
|
||||||
freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && !o.tag);
|
|
||||||
if (!freedomOutbound) return "AsIs";
|
|
||||||
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
|
||||||
return freedomOutbound.settings.domainStrategy;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && !o.tag);
|
|
||||||
if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) {
|
|
||||||
newTemplateSettings.outbounds[freedomOutboundIndex].settings = { "domainStrategy": newValue };
|
|
||||||
} else {
|
|
||||||
newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
routingStrategy: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing.domainStrategy) return "AsIs";
|
|
||||||
return this.templateSettings.routing.domainStrategy;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.routing.domainStrategy = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedIPs: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "ip", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedDomains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "domain", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedProtocols: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "protocol" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "protocol", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directIPs: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "direct", property: "ip" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "direct", property: "ip", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("direct", this.directSettings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directDomains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "direct", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "direct", property: "domain", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("direct", this.directSettings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ipv4Domains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "IPv4", property: "domain", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
manualBlockedIPs: {
|
|
||||||
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
|
|
||||||
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualBlockedDomains: {
|
|
||||||
get: function () { return JSON.stringify(this.blockedDomains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.blockedDomains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualDirectIPs: {
|
|
||||||
get: function () { return JSON.stringify(this.directIPs, null, 2); },
|
|
||||||
set: debounce(function (value) { this.directIPs = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualDirectDomains: {
|
|
||||||
get: function () { return JSON.stringify(this.directDomains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualIPv4Domains: {
|
|
||||||
get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
torrentSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent];
|
|
||||||
} else {
|
|
||||||
this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
privateIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.local, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AdsSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ads, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
familyProtectSettings: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
|
||||||
return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
if (newValue) {
|
|
||||||
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
|
||||||
} else {
|
|
||||||
delete newTemplateSettings.dns;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
GoogleIPv4Settings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
|
|
||||||
} else {
|
|
||||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NetflixIPv4Settings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
|
|
||||||
} else {
|
|
||||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IRIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ir, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ir, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.cn, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.cn, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ru, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ru, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ir, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.cn, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.cn, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ru, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ru, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
1251
web/html/xui/xray.html
Normal file
1251
web/html/xui/xray.html
Normal file
File diff suppressed because it is too large
Load Diff
127
web/html/xui/xray_outbound_modal.html
Normal file
127
web/html/xui/xray_outbound_modal.html
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
{{define "outModal"}}
|
||||||
|
<a-modal id="out-modal" v-model="outModal.visible" :title="outModal.title" @ok="outModal.ok"
|
||||||
|
:confirm-loading="outModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
:ok-button-props="{ props: { disabled: !outModal.isValid } }" style="overflow: hidden;"
|
||||||
|
:ok-text="outModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
|
{{template "form/outbound"}}
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const outModal = {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
isEdit: false,
|
||||||
|
confirm: null,
|
||||||
|
outbound: new Outbound(),
|
||||||
|
jsonMode: false,
|
||||||
|
link: '',
|
||||||
|
cm: null,
|
||||||
|
duplicateTag: false,
|
||||||
|
isValid: true,
|
||||||
|
activeKey: '1',
|
||||||
|
tags: [],
|
||||||
|
ok() {
|
||||||
|
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "sure" }}', outbound, confirm=(outbound)=>{}, isEdit=false, tags=[] }) {
|
||||||
|
this.title = title;
|
||||||
|
this.okText = okText;
|
||||||
|
this.confirm = confirm;
|
||||||
|
this.jsonMode = false;
|
||||||
|
this.link = '';
|
||||||
|
this.activeKey = '1';
|
||||||
|
this.visible = true;
|
||||||
|
this.outbound = isEdit ? Outbound.fromJson(outbound) : new Outbound();
|
||||||
|
this.isEdit = isEdit;
|
||||||
|
this.tags = tags;
|
||||||
|
this.check()
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
outModal.visible = false;
|
||||||
|
outModal.loading(false);
|
||||||
|
},
|
||||||
|
loading(loading) {
|
||||||
|
outModal.confirmLoading = loading;
|
||||||
|
},
|
||||||
|
check(){
|
||||||
|
if(outModal.outbound.tag == '' || outModal.tags.includes(outModal.outbound.tag)){
|
||||||
|
this.duplicateTag = true;
|
||||||
|
this.isValid = false;
|
||||||
|
} else {
|
||||||
|
this.duplicateTag = false;
|
||||||
|
this.isValid = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleJson(jsonTab) {
|
||||||
|
textAreaObj = document.getElementById('outboundJson');
|
||||||
|
if(jsonTab){
|
||||||
|
if(this.cm != null) {
|
||||||
|
this.cm.toTextArea();
|
||||||
|
this.cm=null;
|
||||||
|
}
|
||||||
|
textAreaObj.value = JSON.stringify(this.outbound.toJson(), null, 2);
|
||||||
|
this.cm = CodeMirror.fromTextArea(textAreaObj, app.cmOptions);
|
||||||
|
this.cm.on('change',editor => {
|
||||||
|
value = editor.getValue();
|
||||||
|
if(this.isJsonString(value)){
|
||||||
|
this.outbound = Outbound.fromJson(JSON.parse(value));
|
||||||
|
this.check();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.activeKey = '2';
|
||||||
|
} else {
|
||||||
|
if(this.cm != null) {
|
||||||
|
this.cm.toTextArea();
|
||||||
|
this.cm=null;
|
||||||
|
}
|
||||||
|
this.activeKey = '1';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isJsonString(str) {
|
||||||
|
try {
|
||||||
|
JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#out-modal',
|
||||||
|
data: {
|
||||||
|
outModal: outModal,
|
||||||
|
get outbound() {
|
||||||
|
return outModal.outbound;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
streamNetworkChange() {
|
||||||
|
if (this.outModal.outbound.protocol == Protocols.VLESS && !outModal.outbound.canEnableTlsFlow()) {
|
||||||
|
delete this.outModal.outbound.settings.flow;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canEnableTls() {
|
||||||
|
return this.outModal.outbound.canEnableTls();
|
||||||
|
},
|
||||||
|
convertLink(){
|
||||||
|
newOutbound = Outbound.fromLink(outModal.link);
|
||||||
|
if(newOutbound){
|
||||||
|
this.outModal.outbound = newOutbound;
|
||||||
|
this.outModal.toggleJson(true);
|
||||||
|
this.outModal.check();
|
||||||
|
this.$message.success('Link imported successfully...');
|
||||||
|
outModal.link = '';
|
||||||
|
} else {
|
||||||
|
this.$message.error('Wrong Link!');
|
||||||
|
outModal.link = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
176
web/html/xui/xray_reverse_modal.html
Normal file
176
web/html/xui/xray_reverse_modal.html
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
{{define "reverseModal"}}
|
||||||
|
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
||||||
|
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
|
<a-form layout="inline">
|
||||||
|
<table width="100%" class="ant-table-tbody">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.xray.outbound.type" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="reverseModal.reverse.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.xray.outbound.tag" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="reverseModal.reverse.tag" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.xray.outbound.domain" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="reverseModal.reverse.domain" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-if="reverseModal.reverse.type=='bridge'">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.xray.outbound.intercon" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="reverseModal.rules[0].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.xray.rules.outbound" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="reverseModal.rules[1].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.xray.outbound.intercon" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-checkbox-group
|
||||||
|
v-model="reverseModal.rules[0].inboundTag"
|
||||||
|
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.xray.rules.inbound" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-checkbox-group
|
||||||
|
v-model="reverseModal.rules[1].inboundTag"
|
||||||
|
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
const reverseModal = {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
isEdit: false,
|
||||||
|
confirm: null,
|
||||||
|
reverse: {
|
||||||
|
tag: "",
|
||||||
|
type: "",
|
||||||
|
domain: ""
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{ outboundTag: '', inboundTag: []},
|
||||||
|
{ outboundTag: '', inboundTag: []}
|
||||||
|
],
|
||||||
|
inboundTags: [],
|
||||||
|
outboundTags: [],
|
||||||
|
ok() {
|
||||||
|
reverseModal.rules[0].domain = ["full:" + reverseModal.reverse.domain];
|
||||||
|
reverseModal.rules[0].type = 'field';
|
||||||
|
reverseModal.rules[1].type = 'field';
|
||||||
|
|
||||||
|
if(reverseModal.reverse.type == 'bridge'){
|
||||||
|
reverseModal.rules[0].inboundTag = [reverseModal.reverse.tag];
|
||||||
|
reverseModal.rules[1].inboundTag = [reverseModal.reverse.tag];
|
||||||
|
} else {
|
||||||
|
reverseModal.rules[0].outboundTag = reverseModal.reverse.tag;
|
||||||
|
reverseModal.rules[1].outboundTag = reverseModal.reverse.tag;
|
||||||
|
}
|
||||||
|
ObjectUtil.execute(reverseModal.confirm, reverseModal.reverse, reverseModal.rules);
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "sure" }}', reverse, rules, confirm=(reverse, rules)=>{}, isEdit=false }) {
|
||||||
|
this.title = title;
|
||||||
|
this.okText = okText;
|
||||||
|
this.confirm = confirm;
|
||||||
|
this.visible = true;
|
||||||
|
if(isEdit) {
|
||||||
|
this.reverse = {
|
||||||
|
tag: reverse.tag,
|
||||||
|
type: reverse.type,
|
||||||
|
domain: reverse.domain,
|
||||||
|
};
|
||||||
|
reverse;
|
||||||
|
rules0 = rules.filter(r => r.domain != null);
|
||||||
|
if(rules0.length == 0) rules0 = [{ outboundTag: '', domain: ["full:" + this.reverse.domain], inboundTag: []}];
|
||||||
|
rules1 = rules.filter(r => r.domain == null);
|
||||||
|
if(rules1.length == 0) rules1 = [{ outboundTag: '', inboundTag: []}];
|
||||||
|
this.rules = [];
|
||||||
|
this.rules.push({
|
||||||
|
domain: rules0[0].domain,
|
||||||
|
outboundTag: rules0[0].outboundTag,
|
||||||
|
inboundTag: rules0.map(r => r.inboundTag).flat()
|
||||||
|
});
|
||||||
|
this.rules.push({
|
||||||
|
outboundTag: rules1[0].outboundTag,
|
||||||
|
inboundTag: rules1.map(r => r.inboundTag).flat()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.reverse = {
|
||||||
|
tag: "reverse-" + app.reverseData.length,
|
||||||
|
type: "bridge",
|
||||||
|
domain: "reverse.xui"
|
||||||
|
}
|
||||||
|
this.rules = [
|
||||||
|
{ outboundTag: '', inboundTag: []},
|
||||||
|
{ outboundTag: '', inboundTag: []}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.isEdit = isEdit;
|
||||||
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
|
this.inboundTags.push(...app.inboundTags);
|
||||||
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
reverseModal.visible = false;
|
||||||
|
reverseModal.loading(false);
|
||||||
|
},
|
||||||
|
loading(loading) {
|
||||||
|
reverseModal.confirmLoading = loading;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#reverse-modal',
|
||||||
|
data: {
|
||||||
|
reverseModal: reverseModal,
|
||||||
|
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
277
web/html/xui/xray_rule_modal.html
Normal file
277
web/html/xui/xray_rule_modal.html
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
{{define "ruleModal"}}
|
||||||
|
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
||||||
|
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
|
<a-form layout="inline">
|
||||||
|
<table width="100%" class="ant-table-tbody">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 30%;">Domain Matcher</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="ruleModal.rule.domainMatcher" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Source IPs
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.source" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Source Port
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.sourcePort" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Network</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="ruleModal.rule.network" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x in ['','tcp','tdp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Protocol</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="ruleModal.rule.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<a-form-item>
|
||||||
|
<span>Attributes</span>
|
||||||
|
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
|
||||||
|
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
|
||||||
|
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
|
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>IP
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.ip" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Domain
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.domain" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Port
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.port" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Inbound Tags</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Outbound Tag</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="ruleModal.rule.outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const ruleModal = {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
isEdit: false,
|
||||||
|
confirm: null,
|
||||||
|
rule: {
|
||||||
|
type: "field",
|
||||||
|
domainMatcher: "",
|
||||||
|
domain: "",
|
||||||
|
ip: "",
|
||||||
|
port: "",
|
||||||
|
sourcePort: "",
|
||||||
|
network: "",
|
||||||
|
source: "",
|
||||||
|
user: "",
|
||||||
|
inboundTag: [],
|
||||||
|
protocol: [],
|
||||||
|
attrs: [],
|
||||||
|
outboundTag: "",
|
||||||
|
},
|
||||||
|
inboundTags: [],
|
||||||
|
outboundTags: [],
|
||||||
|
users: [],
|
||||||
|
balancerTag: [],
|
||||||
|
ok() {
|
||||||
|
newRule = ruleModal.getResult();
|
||||||
|
ObjectUtil.execute(ruleModal.confirm, newRule);
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "sure" }}', rule, confirm=(rule)=>{}, isEdit=false }) {
|
||||||
|
this.title = title;
|
||||||
|
this.okText = okText;
|
||||||
|
this.confirm = confirm;
|
||||||
|
this.visible = true;
|
||||||
|
if(isEdit) {
|
||||||
|
this.rule.domainMatcher = rule.domainMatcher;
|
||||||
|
this.rule.domain = rule.domain ? rule.domain.join(',') : [];
|
||||||
|
this.rule.ip = rule.ip ? rule.ip.join(',') : [];
|
||||||
|
this.rule.port = rule.port;
|
||||||
|
this.rule.sourcePort = rule.sourcePort;
|
||||||
|
this.rule.network = rule.network;
|
||||||
|
this.rule.source = rule.source ? rule.source.join(',') : [];
|
||||||
|
this.rule.user = rule.user ? rule.user.join(',') : [];
|
||||||
|
this.rule.inboundTag = rule.inboundTag;
|
||||||
|
this.rule.protocol = rule.protocol;
|
||||||
|
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
||||||
|
this.rule.outboundTag = rule.outboundTag;
|
||||||
|
} else {
|
||||||
|
this.rule = {
|
||||||
|
domainMatcher: "",
|
||||||
|
domain: "",
|
||||||
|
ip: "",
|
||||||
|
port: "",
|
||||||
|
sourcePort: "",
|
||||||
|
network: "",
|
||||||
|
source: "",
|
||||||
|
user: "",
|
||||||
|
inboundTag: [],
|
||||||
|
protocol: [],
|
||||||
|
attrs: [],
|
||||||
|
outboundTag: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isEdit = isEdit;
|
||||||
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
|
this.inboundTags.push(...app.inboundTags);
|
||||||
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||||
|
if(app.templateSettings.reverse){
|
||||||
|
if(app.templateSettings.reverse.bridges) {
|
||||||
|
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
||||||
|
}
|
||||||
|
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
ruleModal.visible = false;
|
||||||
|
ruleModal.loading(false);
|
||||||
|
},
|
||||||
|
loading(loading) {
|
||||||
|
ruleModal.confirmLoading = loading;
|
||||||
|
},
|
||||||
|
getResult() {
|
||||||
|
value = ruleModal.rule;
|
||||||
|
rule = {};
|
||||||
|
newRule = {};
|
||||||
|
rule.type = "field";
|
||||||
|
rule.domainMatcher = value.domainMatcher;
|
||||||
|
rule.domain = value.domain.length>0 ? value.domain.split(',') : [];
|
||||||
|
rule.ip = value.ip.length>0 ? value.ip.split(',') : [];
|
||||||
|
rule.port = value.port;
|
||||||
|
rule.sourcePort = value.sourcePort;
|
||||||
|
rule.network = value.network;
|
||||||
|
rule.source = value.source.length>0 ? value.source.split(',') : [];
|
||||||
|
rule.user = value.user.length>0 ? value.user.split(',') : [];
|
||||||
|
rule.inboundTag = value.inboundTag;
|
||||||
|
rule.protocol = value.protocol;
|
||||||
|
rule.attrs = Object.fromEntries(value.attrs);
|
||||||
|
rule.outboundTag = value.outboundTag;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(rule)) {
|
||||||
|
if (
|
||||||
|
value !== null &&
|
||||||
|
value !== undefined &&
|
||||||
|
!(Array.isArray(value) && value.length === 0) &&
|
||||||
|
!(typeof value === 'object' && Object.keys(value).length === 0) &&
|
||||||
|
value !== ''
|
||||||
|
) {
|
||||||
|
newRule[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newRule;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#rule-modal',
|
||||||
|
data: {
|
||||||
|
ruleModal: ruleModal,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
|
"tag": "direct",
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {}
|
"settings": {}
|
||||||
},
|
},
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "AsIs",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
|||||||
@@ -168,9 +168,13 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
|||||||
|
|
||||||
err = tx.Save(inbound).Error
|
err = tx.Save(inbound).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, client := range clients {
|
if len(inbound.ClientStats) == 0 {
|
||||||
s.AddClientStat(tx, inbound.Id, &client)
|
for _, client := range clients {
|
||||||
|
s.AddClientStat(tx, inbound.Id, &client)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return inbound, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart := false
|
needRestart := false
|
||||||
@@ -249,7 +253,18 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
|
|
||||||
tag := oldInbound.Tag
|
tag := oldInbound.Tag
|
||||||
|
|
||||||
err = s.updateClientTraffics(oldInbound, inbound)
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = s.updateClientTraffics(tx, oldInbound, inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, false, err
|
return inbound, false, err
|
||||||
}
|
}
|
||||||
@@ -290,11 +305,10 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
}
|
}
|
||||||
s.xrayApi.Close()
|
s.xrayApi.Close()
|
||||||
|
|
||||||
db := database.GetDB()
|
return inbound, needRestart, tx.Save(oldInbound).Error
|
||||||
return inbound, needRestart, db.Save(oldInbound).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbound *model.Inbound) error {
|
func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inbound, newInbound *model.Inbound) error {
|
||||||
oldClients, err := s.GetClients(oldInbound)
|
oldClients, err := s.GetClients(oldInbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -304,17 +318,6 @@ func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db := database.GetDB()
|
|
||||||
tx := db.Begin()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
} else {
|
|
||||||
tx.Commit()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var emailExists bool
|
var emailExists bool
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
@@ -581,7 +584,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
|
|
||||||
if len(clients[0].Email) > 0 {
|
if len(clients[0].Email) > 0 {
|
||||||
if len(oldEmail) > 0 {
|
if len(oldEmail) > 0 {
|
||||||
err = s.UpdateClientStat(oldEmail, &clients[0])
|
err = s.UpdateClientStat(tx, oldEmail, &clients[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -648,6 +651,13 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
|
|||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needRestart0, count, err := s.autoRenewClients(tx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Error in renew clients:", err)
|
||||||
|
} else if count > 0 {
|
||||||
|
logger.Debugf("%v clients renewed", count)
|
||||||
|
}
|
||||||
|
|
||||||
needRestart1, count, err := s.disableInvalidClients(tx)
|
needRestart1, count, err := s.disableInvalidClients(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Error in disabling invalid clients:", err)
|
logger.Warning("Error in disabling invalid clients:", err)
|
||||||
@@ -661,7 +671,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
|
|||||||
} else if count > 0 {
|
} else if count > 0 {
|
||||||
logger.Debugf("%v inbounds disabled", count)
|
logger.Debugf("%v inbounds disabled", count)
|
||||||
}
|
}
|
||||||
return nil, (needRestart1 || needRestart2)
|
return nil, (needRestart0 || needRestart1 || needRestart2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
||||||
@@ -688,9 +698,15 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
|
|||||||
|
|
||||||
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
|
// Empty onlineUsers
|
||||||
|
if p != nil {
|
||||||
|
p.SetOnlineClients(nil)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onlineClients []string
|
||||||
|
|
||||||
emails := make([]string, 0, len(traffics))
|
emails := make([]string, 0, len(traffics))
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
emails = append(emails, traffic.Email)
|
emails = append(emails, traffic.Email)
|
||||||
@@ -716,11 +732,19 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
|||||||
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
|
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
|
||||||
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
|
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
|
||||||
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
|
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
|
||||||
|
|
||||||
|
// Add user in onlineUsers array on traffic
|
||||||
|
if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 {
|
||||||
|
onlineClients = append(onlineClients, traffics[traffic_index].Email)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set onlineUsers
|
||||||
|
p.SetOnlineClients(onlineClients)
|
||||||
|
|
||||||
err = tx.Save(dbClientTraffics).Error
|
err = tx.Save(dbClientTraffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("AddClientTraffic update data ", err)
|
logger.Warning("AddClientTraffic update data ", err)
|
||||||
@@ -781,6 +805,102 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
|||||||
return dbClientTraffics, nil
|
return dbClientTraffics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||||
|
// check for time expired
|
||||||
|
var traffics []*xray.ClientTraffic
|
||||||
|
now := time.Now().Unix() * 1000
|
||||||
|
var err, err1 error
|
||||||
|
|
||||||
|
err = tx.Model(xray.ClientTraffic{}).Where("reset > 0 and expiry_time > 0 and expiry_time <= ?", now).Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
// return if there is no client to renew
|
||||||
|
if len(traffics) == 0 {
|
||||||
|
return false, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbound_ids []int
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
needRestart := false
|
||||||
|
var clientsToAdd []struct {
|
||||||
|
protocol string
|
||||||
|
tag string
|
||||||
|
client map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
inbound_ids = append(inbound_ids, traffic.InboundId)
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Inbound{}).Where("id IN ?", inbound_ids).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
for inbound_index := range inbounds {
|
||||||
|
settings := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
for traffic_index, traffic := range traffics {
|
||||||
|
if traffic.Email == c["email"].(string) {
|
||||||
|
newExpiryTime := traffic.ExpiryTime
|
||||||
|
for newExpiryTime < now {
|
||||||
|
newExpiryTime += (int64(traffic.Reset) * 86400000)
|
||||||
|
}
|
||||||
|
c["expiryTime"] = newExpiryTime
|
||||||
|
traffics[traffic_index].ExpiryTime = newExpiryTime
|
||||||
|
traffics[traffic_index].Down = 0
|
||||||
|
traffics[traffic_index].Up = 0
|
||||||
|
if !traffic.Enable {
|
||||||
|
traffics[traffic_index].Enable = true
|
||||||
|
clientsToAdd = append(clientsToAdd,
|
||||||
|
struct {
|
||||||
|
protocol string
|
||||||
|
tag string
|
||||||
|
client map[string]interface{}
|
||||||
|
}{
|
||||||
|
protocol: string(inbounds[inbound_index].Protocol),
|
||||||
|
tag: inbounds[inbound_index].Tag,
|
||||||
|
client: c,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
clients[client_index] = interface{}(c)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = clients
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
inbounds[inbound_index].Settings = string(newSettings)
|
||||||
|
}
|
||||||
|
err = tx.Save(inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
err = tx.Save(traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
if p != nil {
|
||||||
|
err1 = s.xrayApi.Init(p.GetAPIPort())
|
||||||
|
if err1 != nil {
|
||||||
|
return true, int64(len(traffics)), nil
|
||||||
|
}
|
||||||
|
for _, clientToAdd := range clientsToAdd {
|
||||||
|
err1 = s.xrayApi.AddUser(clientToAdd.protocol, clientToAdd.tag, clientToAdd.client)
|
||||||
|
if err1 != nil {
|
||||||
|
needRestart = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.xrayApi.Close()
|
||||||
|
}
|
||||||
|
return needRestart, int64(len(traffics)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
needRestart := false
|
needRestart := false
|
||||||
@@ -874,6 +994,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
|||||||
clientTraffic.Enable = true
|
clientTraffic.Enable = true
|
||||||
clientTraffic.Up = 0
|
clientTraffic.Up = 0
|
||||||
clientTraffic.Down = 0
|
clientTraffic.Down = 0
|
||||||
|
clientTraffic.Reset = client.Reset
|
||||||
result := tx.Create(&clientTraffic)
|
result := tx.Create(&clientTraffic)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -882,16 +1003,15 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
|
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
||||||
db := database.GetDB()
|
result := tx.Model(xray.ClientTraffic{}).
|
||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
|
||||||
Where("email = ?", email).
|
Where("email = ?", email).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"total": client.TotalGB,
|
"total": client.TotalGB,
|
||||||
"expiry_time": client.ExpiryTime})
|
"expiry_time": client.ExpiryTime,
|
||||||
|
"reset": client.Reset})
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1011,7 +1131,7 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
whereText := "inbound_id "
|
whereText := "reset = 0 and inbound_id "
|
||||||
if id < 0 {
|
if id < 0 {
|
||||||
whereText += "> ?"
|
whereText += "> ?"
|
||||||
} else {
|
} else {
|
||||||
@@ -1078,27 +1198,16 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
|
func (s *InboundService) GetClientTrafficTgBot(tgid string, tguname string) ([]*xray.ClientTraffic, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
|
||||||
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": "%s"%%`, tguname)).Find(&inbounds).Error
|
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var emails []string
|
|
||||||
for _, inbound := range inbounds {
|
|
||||||
clients, err := s.GetClients(inbound)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Unable to get clients from inbound")
|
|
||||||
}
|
|
||||||
for _, client := range clients {
|
|
||||||
if client.TgID == tguname {
|
|
||||||
emails = append(emails, client.Email)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var traffics []*xray.ClientTraffic
|
var traffics []*xray.ClientTraffic
|
||||||
err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error
|
err := db.Model(xray.ClientTraffic{}).Where(`email IN(
|
||||||
|
SELECT JSON_EXTRACT(client.value, '$.email') as email
|
||||||
|
FROM inbounds,
|
||||||
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
|
WHERE
|
||||||
|
JSON_EXTRACT(client.value, '$.tgId') in (?,?)
|
||||||
|
)`, tgid, tguname).Find(&traffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
@@ -1173,6 +1282,17 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
|
|||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetInboundTags() (string, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inboundTags []string
|
||||||
|
err := db.Model(model.Inbound{}).Select("tag").Find(&inboundTags).Error
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tags, _ := json.Marshal(inboundTags)
|
||||||
|
return string(tags), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrationRequirements() {
|
func (s *InboundService) MigrationRequirements() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
@@ -1242,9 +1362,53 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
|
|
||||||
// Remove orphaned traffics
|
// Remove orphaned traffics
|
||||||
tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||||
|
|
||||||
|
// Migrate old MultiDomain to External Proxy
|
||||||
|
var externalProxy []struct {
|
||||||
|
Id int
|
||||||
|
Port int
|
||||||
|
StreamSettings []byte
|
||||||
|
}
|
||||||
|
err = tx.Raw(`select id, port, stream_settings
|
||||||
|
from inbounds
|
||||||
|
WHERE protocol in ('vmess','vless','trojan')
|
||||||
|
AND json_extract(stream_settings, '$.security') = 'tls'
|
||||||
|
AND json_extract(stream_settings, '$.tlsSettings.settings.domains') IS NOT NULL`).Scan(&externalProxy).Error
|
||||||
|
if err != nil || len(externalProxy) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ep := range externalProxy {
|
||||||
|
var reverses interface{}
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal(ep.StreamSettings, &stream)
|
||||||
|
if tlsSettings, ok := stream["tlsSettings"].(map[string]interface{}); ok {
|
||||||
|
if settings, ok := tlsSettings["settings"].(map[string]interface{}); ok {
|
||||||
|
if domains, ok := settings["domains"].([]interface{}); ok {
|
||||||
|
for _, domain := range domains {
|
||||||
|
if domainMap, ok := domain.(map[string]interface{}); ok {
|
||||||
|
domainMap["forceTls"] = "same"
|
||||||
|
domainMap["port"] = ep.Port
|
||||||
|
domainMap["dest"] = domainMap["domain"].(string)
|
||||||
|
delete(domainMap, "domain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reverses = settings["domains"]
|
||||||
|
delete(settings, "domains")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream["externalProxy"] = reverses
|
||||||
|
newStream, _ := json.MarshalIndent(stream, " ", " ")
|
||||||
|
tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrateDB() {
|
func (s *InboundService) MigrateDB() {
|
||||||
s.MigrationRequirements()
|
s.MigrationRequirements()
|
||||||
s.MigrationRemoveOrphanedTraffics()
|
s.MigrationRemoveOrphanedTraffics()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetOnlineClinets() []string {
|
||||||
|
return p.GetOnlineClients()
|
||||||
|
}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
status.AppStats.Mem = rtm.Sys
|
status.AppStats.Mem = rtm.Sys
|
||||||
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
||||||
status.CpuCount = runtime.NumCPU()
|
status.CpuCount = runtime.NumCPU()
|
||||||
if p.IsRunning() {
|
if p != nil && p.IsRunning() {
|
||||||
status.AppStats.Uptime = p.GetUptime()
|
status.AppStats.Uptime = p.GetUptime()
|
||||||
} else {
|
} else {
|
||||||
status.AppStats.Uptime = 0
|
status.AppStats.Uptime = 0
|
||||||
@@ -363,14 +363,6 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = copyZipFile("geosite.dat", xray.GetGeositePath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = copyZipFile("geoip.dat", xray.GetGeoipPath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
@@ -418,6 +410,11 @@ func (s *ServerService) GetConfigJson() (interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetDb() ([]byte, error) {
|
func (s *ServerService) GetDb() ([]byte, error) {
|
||||||
|
// Update by manually trigger a checkpoint operation
|
||||||
|
err := database.Checkpoint()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// Open the file for reading
|
// Open the file for reading
|
||||||
file, err := os.Open(config.GetDBPath())
|
file, err := os.Open(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ var defaultValueMap = map[string]string{
|
|||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
"webBasePath": "/",
|
"webBasePath": "/",
|
||||||
"sessionMaxAge": "0",
|
"sessionMaxAge": "0",
|
||||||
|
"pageSize": "0",
|
||||||
"expireDiff": "0",
|
"expireDiff": "0",
|
||||||
"trafficDiff": "0",
|
"trafficDiff": "0",
|
||||||
|
"remarkModel": "-ieo",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Asia/Tehran",
|
||||||
"tgBotEnable": "false",
|
"tgBotEnable": "false",
|
||||||
"tgBotToken": "",
|
"tgBotToken": "",
|
||||||
@@ -52,6 +54,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"subUpdates": "12",
|
"subUpdates": "12",
|
||||||
"subEncrypt": "true",
|
"subEncrypt": "true",
|
||||||
"subShowInfo": "false",
|
"subShowInfo": "false",
|
||||||
|
"subURI": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -60,7 +63,7 @@ type SettingService struct {
|
|||||||
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
settings := make([]*model.Setting, 0)
|
settings := make([]*model.Setting, 0)
|
||||||
err := db.Model(model.Setting{}).Find(&settings).Error
|
err := db.Model(model.Setting{}).Not("key = ?", "xrayTemplateConfig").Find(&settings).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -293,6 +296,10 @@ func (s *SettingService) GetSessionMaxAge() (int, error) {
|
|||||||
return s.getInt("sessionMaxAge")
|
return s.getInt("sessionMaxAge")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetRemarkModel() (string, error) {
|
||||||
|
return s.getString("remarkModel")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||||
secret, err := s.getString("secret")
|
secret, err := s.getString("secret")
|
||||||
if secret == defaultValueMap["secret"] {
|
if secret == defaultValueMap["secret"] {
|
||||||
@@ -382,6 +389,14 @@ func (s *SettingService) GetSubShowInfo() (bool, error) {
|
|||||||
return s.getBool("subShowInfo")
|
return s.getBool("subShowInfo")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetPageSize() (int, error) {
|
||||||
|
return s.getInt("pageSize")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubURI() (string, error) {
|
||||||
|
return s.getString("subURI")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||||
if err := allSetting.CheckValid(); err != nil {
|
if err := allSetting.CheckValid(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -411,3 +426,62 @@ func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
|||||||
}
|
}
|
||||||
return jsonData, nil
|
return jsonData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||||
|
type settingFunc func() (interface{}, error)
|
||||||
|
settings := map[string]settingFunc{
|
||||||
|
"expireDiff": func() (interface{}, error) { return s.GetExpireDiff() },
|
||||||
|
"trafficDiff": func() (interface{}, error) { return s.GetTrafficDiff() },
|
||||||
|
"pageSize": func() (interface{}, error) { return s.GetPageSize() },
|
||||||
|
"defaultCert": func() (interface{}, error) { return s.GetCertFile() },
|
||||||
|
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
||||||
|
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
||||||
|
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||||
|
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||||
|
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
|
||||||
|
for key, fn := range settings {
|
||||||
|
value, err := fn()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if result["subEnable"].(bool) && result["subURI"].(string) == "" {
|
||||||
|
subURI := ""
|
||||||
|
subPort, _ := s.GetSubPort()
|
||||||
|
subPath, _ := s.GetSubPath()
|
||||||
|
subDomain, _ := s.GetSubDomain()
|
||||||
|
subKeyFile, _ := s.GetSubKeyFile()
|
||||||
|
subCertFile, _ := s.GetSubCertFile()
|
||||||
|
subTLS := false
|
||||||
|
if subKeyFile != "" && subCertFile != "" {
|
||||||
|
subTLS = true
|
||||||
|
}
|
||||||
|
if subDomain == "" {
|
||||||
|
subDomain = strings.Split(host, ":")[0]
|
||||||
|
}
|
||||||
|
if subTLS {
|
||||||
|
subURI = "https://"
|
||||||
|
} else {
|
||||||
|
subURI = "http://"
|
||||||
|
}
|
||||||
|
if (subPort == 443 && subTLS) || (subPort == 80 && !subTLS) {
|
||||||
|
subURI += subDomain
|
||||||
|
} else {
|
||||||
|
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
|
||||||
|
}
|
||||||
|
if subPath[0] == byte('/') {
|
||||||
|
subURI += subPath
|
||||||
|
} else {
|
||||||
|
subURI += "/" + subPath
|
||||||
|
}
|
||||||
|
result["subURI"] = subURI
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user