mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-20 07:45:48 +00:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||||
|
|||||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -10,7 +10,7 @@ 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@v4
|
||||||
with:
|
with:
|
||||||
@@ -26,7 +26,7 @@ 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 iran.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -49,7 +49,7 @@ 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@v4
|
||||||
with:
|
with:
|
||||||
@@ -67,7 +67,7 @@ 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-64.zip geoip.dat geosite.dat iran.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -90,7 +90,7 @@ 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@v4
|
||||||
with:
|
with:
|
||||||
@@ -108,7 +108,7 @@ 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-64.zip geoip.dat geosite.dat iran.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ dist/
|
|||||||
release/
|
release/
|
||||||
/release.sh
|
/release.sh
|
||||||
/x-ui
|
/x-ui
|
||||||
|
.DS_Store
|
||||||
@@ -11,7 +11,7 @@ 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 iran.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|||||||
43
README.md
43
README.md
@@ -22,6 +22,8 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russia
|
|||||||
| Backup database using Telegram BOT | :heavy_check_mark: |
|
| Backup database using Telegram BOT | :heavy_check_mark: |
|
||||||
| Subscription link + userInfo | :heavy_check_mark: |
|
| Subscription link + userInfo | :heavy_check_mark: |
|
||||||
| Calculate expire date on first usage | :heavy_check_mark: |
|
| Calculate expire date on first usage | :heavy_check_mark: |
|
||||||
|
| Show Online Clients | :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:
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.s
|
|||||||
## 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
|
||||||
|
|
||||||
@@ -110,6 +112,7 @@ docker build -t x-ui .
|
|||||||
- Support one-click SSL certificate application and automatic renewal
|
- Support one-click SSL certificate application and automatic renewal
|
||||||
- For more advanced configuration items, please refer to the panel
|
- For more advanced configuration items, please refer to the panel
|
||||||
- Support export/import database from panel
|
- Support export/import database from panel
|
||||||
|
- Show online users
|
||||||
|
|
||||||
## suggestion system
|
## suggestion system
|
||||||
|
|
||||||
@@ -123,21 +126,23 @@ docker build -t x-ui .
|
|||||||
- `/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:
|
||||||
|
|
||||||
@@ -154,10 +159,12 @@ 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
|
# Screenshots
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## SSL certificate application
|
## SSL certificate application
|
||||||
|
|
||||||
@@ -206,7 +213,7 @@ Reference syntax:
|
|||||||
- 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 )
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.5.5
|
1.6.1
|
||||||
@@ -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.2.2
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|||||||
203
go.sum
203
go.sum
@@ -11,10 +11,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
|
github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
|
||||||
github.com/Workiva/go-datastructures v1.1.0/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.6/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 +30,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 +52,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 +111,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 +132,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 +148,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 +173,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 +181,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 +192,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.2.2 h1:Iv/FL6pvYmDqybEZkr4TrOv8jSHezwpE77K68kcaft8=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
github.com/nicksnyder/go-i18n/v2 v2.2.2/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
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 +222,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 +289,41 @@ 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/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 +336,20 @@ 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-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -335,10 +362,9 @@ 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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||||
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 +376,9 @@ 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-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 +387,37 @@ 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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.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/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-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 +425,17 @@ 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-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,21 +448,24 @@ 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=
|
||||||
@@ -441,15 +475,16 @@ 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 |
@@ -53,6 +53,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)
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,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 +204,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 +215,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 +240,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 +338,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 +349,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 +363,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" {
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,6 +586,44 @@ func (s *SubService) genTrojanLink(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("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)
|
||||||
|
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
@@ -575,20 +636,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 +705,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,6 +787,7 @@ 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()
|
||||||
}
|
}
|
||||||
|
|||||||
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
76
web/assets/codemirror/xq.css
Normal file
76
web/assets/codemirror/xq.css
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
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 span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; }
|
||||||
|
.cm-s-xq span.cm-atom { color: #6C8CD5; }
|
||||||
|
.cm-s-xq span.cm-number { color: #164; }
|
||||||
|
.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 {}
|
||||||
|
.cm-s-xq span.cm-operator {}
|
||||||
|
.cm-s-xq span.cm-comment { color: #0080FF; font-style: italic; }
|
||||||
|
.cm-s-xq span.cm-string { color: #e04141; }
|
||||||
|
.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 div.CodeMirror-selected { background: #27007A; }
|
||||||
|
.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: #222D42; border-right: 1px solid #2c3950; }
|
||||||
|
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
|
||||||
|
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: #f8f8f8; }
|
||||||
|
.dark .cm-s-xq .CodeMirror-linenumber { color: #f8f8f8; }
|
||||||
|
.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: #6C8CD5; }
|
||||||
|
.dark .cm-s-xq span.cm-number { color: #164; }
|
||||||
|
.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 {}
|
||||||
|
.dark .cm-s-xq span.cm-operator {}
|
||||||
|
.dark .cm-s-xq span.cm-comment { color: gray; }
|
||||||
|
.dark .cm-s-xq span.cm-string { color: #9FEE00; }
|
||||||
|
.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; }
|
||||||
@@ -7,6 +7,28 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: rgba(0,0,0,.65);
|
||||||
|
font-size: 14px;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
line-height: 1.5;
|
||||||
|
background-color: #fff;
|
||||||
|
font-feature-settings: "tnum";
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
--antd-wave-shadow-color: #0e49b5;
|
||||||
|
line-height: 1.15;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-ms-overflow-style: scrollbar;
|
||||||
|
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
color: #0e49b5;
|
||||||
|
background-color: #0e49b530;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -19,6 +41,73 @@ body {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-layout, .ant-layout * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-spin-blur {
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
style attribute {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.ant-table-tbody>tr>td, .ant-table-thead>tr>th {
|
||||||
|
padding: 16px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
.ant-table-thead>tr>th {
|
||||||
|
color: rgba(0,0,0,.85);
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
transition: background .3s ease;
|
||||||
|
}
|
||||||
|
.ant-table-row-cell-break-word {
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table table {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 1rem 1rem 0 0;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
.ant-table {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: rgba(0,0,0,.65);
|
||||||
|
font-size: 14px;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
line-height: 1.5;
|
||||||
|
list-style: none;
|
||||||
|
font-feature-settings: "tnum";
|
||||||
|
position: relative;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.ant-card-hoverable {
|
||||||
|
cursor: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ant-card {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: rgba(0,0,0,.65);
|
||||||
|
font-size: 14px;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
line-height: 1.5;
|
||||||
|
list-style: none;
|
||||||
|
font-feature-settings: "tnum";
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all .3s;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-space {
|
.ant-space {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -31,10 +120,22 @@ body {
|
|||||||
.ant-layout-sider {
|
.ant-layout-sider {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.ant-card {
|
||||||
|
margin: .5rem;
|
||||||
|
}
|
||||||
|
.ant-tabs {
|
||||||
|
margin: .5rem;
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card {
|
.ant-layout-content {
|
||||||
border-radius: 30px;
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card,
|
||||||
|
.ant-tabs {
|
||||||
|
border-radius: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-hoverable {
|
.ant-card-hoverable {
|
||||||
@@ -64,13 +165,78 @@ body {
|
|||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||||
|
background-color: #04308f !important;
|
||||||
|
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 linear 6.6s infinite;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0.5rem
|
||||||
|
}
|
||||||
|
@-webkit-keyframes ma-bg-move {
|
||||||
|
0% {background-position: -500px 0;}
|
||||||
|
100% {background-position: 1000px 0;}
|
||||||
|
}
|
||||||
|
@keyframes ma-bg-move {
|
||||||
|
0% {background-position: -500px 0;}
|
||||||
|
50% {background-position: 1000px 0;}
|
||||||
|
100% {background-position: 1000px 0;}
|
||||||
|
}
|
||||||
|
.ant-menu-item-active,
|
||||||
|
.ant-menu-item:hover,
|
||||||
|
.ant-menu-submenu-active,
|
||||||
|
.ant-menu-submenu-title:hover,
|
||||||
|
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{
|
||||||
|
color:#0e49b5;
|
||||||
|
background-color: #dce9f5;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-inline .ant-menu-item {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-inline .ant-menu-item:after,
|
||||||
|
.ant-menu {
|
||||||
|
border-right-width: 0;
|
||||||
|
}
|
||||||
|
.ant-layout-sider-children,
|
||||||
|
.ant-pagination ul {
|
||||||
|
margin-top:-.1px;
|
||||||
|
padding:0.5rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu,
|
||||||
|
.ant-select-dropdown-menu {
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
.ant-dropdown-menu-item,
|
||||||
|
.ant-dropdown-menu-item:hover,
|
||||||
|
.ant-select-dropdown-menu-item,
|
||||||
|
.ant-select-dropdown-menu-item:hover,
|
||||||
|
.ant-select-dropdown-menu-item-selected,
|
||||||
|
.ant-select-selection--multiple .ant-select-selection__choice {
|
||||||
|
border-radius: .5rem;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
.drawer-handle {
|
.drawer-handle {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.ant-tabs {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-enter, .fade-in-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active {
|
.fade-in-enter,
|
||||||
|
.fade-in-leave-active,
|
||||||
|
.fade-in-linear-enter,
|
||||||
|
.fade-in-linear-leave,
|
||||||
|
.fade-in-linear-leave-active,
|
||||||
|
.fade-in-linear-enter,
|
||||||
|
.fade-in-linear-leave,
|
||||||
|
.fade-in-linear-leave-active {
|
||||||
opacity: 0
|
opacity: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +332,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-list-item-meta-title {
|
.ant-list-item-meta-title {
|
||||||
font-weight: bold;
|
font-size: 14px;
|
||||||
font-size: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-progress-inner {
|
.ant-progress-inner {
|
||||||
@@ -181,7 +346,7 @@ body {
|
|||||||
|
|
||||||
.ant-table-tbody>tr>td,
|
.ant-table-tbody>tr>td,
|
||||||
.ant-table-thead>tr>th{
|
.ant-table-thead>tr>th{
|
||||||
padding:16px;
|
padding:16px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-expand-icon-th,
|
.ant-table-expand-icon-th,
|
||||||
@@ -190,145 +355,14 @@ body {
|
|||||||
min-width: 30px;
|
min-width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-dark,
|
.ant-tabs {
|
||||||
.ant-menu-dark .ant-menu-sub,
|
|
||||||
.ant-layout-header,
|
|
||||||
.ant-layout-sider-dark,
|
|
||||||
.ant-layout-sider-zero-width-trigger,
|
|
||||||
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
|
||||||
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
|
||||||
background:#1a212a
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tabs:not(.ant-card-dark) {
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #1a212a;
|
|
||||||
border-color:rgba(0,0,0,.09);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark:hover {
|
|
||||||
border-color: #e8e8e8;
|
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-setting-textarea {
|
.ant-setting-textarea {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark-box-nohover{
|
|
||||||
padding: 0 20px 20px !important;
|
|
||||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
|
||||||
}
|
|
||||||
.ant-card-dark-box-nohover:hover{
|
|
||||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-table-thead th {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #161b22;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-table-tbody tr td,
|
|
||||||
.ant-card-dark .ant-modal-title {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-collapse-content,
|
|
||||||
.ant-card-dark .ant-calendar,
|
|
||||||
.ant-card-dark .ant-table-placeholder,
|
|
||||||
.ant-card-dark .ant-select-selection__choice,
|
|
||||||
.ant-card-dark .ant-input-group-addon {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #262f3d;
|
|
||||||
border: 1px solid rgb(0 150 112 / 0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-list-item-meta-title,
|
|
||||||
.ant-card-dark .ant-list-item-meta-description,
|
|
||||||
.ant-card-dark .ant-form-item-label>label,
|
|
||||||
.ant-card-dark .ant-form-item,
|
|
||||||
.ant-card-dark .ant-divider-inner-text,
|
|
||||||
.ant-card-dark .ant-modal-confirm-content,
|
|
||||||
.ant-card-dark .ant-modal-confirm-title,
|
|
||||||
.ant-card-dark .ant-progress-text,
|
|
||||||
.ant-card-dark .ant-modal-close,
|
|
||||||
.ant-card-dark i,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item,
|
|
||||||
.ant-card-dark .ant-calendar-day-select,
|
|
||||||
.ant-card-dark .ant-calendar-month-select,
|
|
||||||
.ant-card-dark .ant-calendar-year-select,
|
|
||||||
.ant-card-dark .ant-calendar-date,
|
|
||||||
.ant-card-dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,
|
|
||||||
.ant-card-dark .ant-empty-normal,
|
|
||||||
.ant-card-dark .ant-checkbox+span {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
|
||||||
.ant-card-dark .ant-calendar-date:hover,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
|
||||||
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
|
||||||
background-color: #11314d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark tbody .ant-table-expanded-row,
|
|
||||||
.ant-card-dark .ant-calendar-time-picker-inner {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #1a212a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input-number {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-input,
|
|
||||||
.ant-card-dark .ant-input-number,
|
|
||||||
.ant-card-dark .ant-input-number-handler-wrap,
|
|
||||||
.ant-card-dark .ant-calendar-input,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item-selected,
|
|
||||||
.ant-card-dark .ant-select-selection,
|
|
||||||
.ant-card-dark .ant-calendar-picker-clear {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #193752;
|
|
||||||
border: 1px solid rgba(0, 65, 150, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
background-color: #242c3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-collapse-item {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #161b22;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-dropdown-menu-dark,
|
|
||||||
.ant-card-dark .ant-modal-content {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.65);
|
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-modal-content,
|
|
||||||
.ant-card-dark .ant-modal-body,
|
|
||||||
.ant-card-dark .ant-modal-header {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #222a37;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
|
||||||
background-color: #1668dc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-calendar-time-picker-select li:hover {
|
|
||||||
background: #1668dc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-table-header {
|
.client-table-header {
|
||||||
background-color: #f0f2f5;
|
background-color: #f0f2f5;
|
||||||
}
|
}
|
||||||
@@ -337,114 +371,384 @@ body {
|
|||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .client-table-header {
|
.ant-table-pagination.ant-pagination {
|
||||||
background-color: #1a212a;
|
float: left;
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .client-table-odd-row {
|
/* change basic colors */
|
||||||
color: hsla(0,0%,100%,.65);
|
.ant-tag-blue {
|
||||||
background-color: #242c3a;
|
background-color: #edf4fa;
|
||||||
|
border-color: #a9c5e7;
|
||||||
|
color: #0e49b5;
|
||||||
|
}
|
||||||
|
.ant-tag-green {
|
||||||
|
background-color: #f6ffed;
|
||||||
|
border-color: #b7eb8f;
|
||||||
|
color: #389e0d;
|
||||||
|
}
|
||||||
|
.ant-tag-purple {
|
||||||
|
background-color: #f2eaf1;
|
||||||
|
border-color: #d5bed2;
|
||||||
|
color: #7a316f;
|
||||||
|
}
|
||||||
|
.ant-tag-orange,
|
||||||
|
.ant-alert-warning {
|
||||||
|
background-color:#fff6E6;
|
||||||
|
border-color: #ffd98c;
|
||||||
|
color: #ffa031;
|
||||||
|
}
|
||||||
|
.ant-tag-red,
|
||||||
|
.ant-alert-error {
|
||||||
|
background-color:#fff0f0;
|
||||||
|
border-color: #fb9d9d;
|
||||||
|
color: #e04141;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-calendar-last-month-cell .ant-calendar-date,
|
.ant-input::placeholder{
|
||||||
.ant-card-dark .ant-calendar-next-month-btn-day .ant-calendar-date {
|
opacity: 0.5;
|
||||||
color: hsla(0,0%,100%,.30);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-drawer-dark {
|
.ant-input:hover,
|
||||||
color: hsla(0,0%,100%,.65);
|
.ant-input:focus {
|
||||||
|
background-color: #edf4fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-drawer-dark .ant-drawer-wrapper-body,
|
.delete-icon:hover {
|
||||||
.ant-drawer-dark .drawer-handle {
|
color: #E04141;
|
||||||
background-color: #1a212a;
|
|
||||||
border: 1px solid hsla(0,0%,100%,.30);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-tag {
|
.normal-icon:hover {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: #0E49B5;
|
||||||
background: rgba(255,255,255,.04);
|
|
||||||
border-color: #434343;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-blue {
|
/* DARK THEME */
|
||||||
color: #3c9ae8;
|
|
||||||
background: #111d2c;
|
.dark ::selection {
|
||||||
border-color: #15395b;
|
color: #fff;
|
||||||
|
background-color: #0e49b5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-green {
|
.dark .normal-icon:hover {
|
||||||
color: #6abe39;
|
color: #ffffff;
|
||||||
background: #162312;
|
|
||||||
border-color: #274916;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-cyan {
|
.dark .ant-layout-sider,
|
||||||
color: #33bcb7;
|
.dark .ant-drawer-content,
|
||||||
background: #112123;
|
.ant-menu-dark,
|
||||||
border-color: #144848;
|
.ant-menu-dark .ant-menu-sub,
|
||||||
|
.dark .ant-card,
|
||||||
|
.dark .ant-table,
|
||||||
|
.dark .ant-collapse-content,
|
||||||
|
.dark .ant-tabs {
|
||||||
|
background-color: #151F31;
|
||||||
|
color: #ffffffa6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-red {
|
.dark .ant-card-hoverable:hover,
|
||||||
color: #e84749;
|
.dark .ant-space-item>.ant-tabs:hover {
|
||||||
background: #2a1215;
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%);
|
||||||
border-color: #58181c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-orange {
|
.dark>.ant-layout,
|
||||||
color: #e89a3c;
|
.dark .drawer-handle,
|
||||||
background: #2b1d11;
|
.dark .ant-table-thead>tr>th,
|
||||||
border-color: #593815;
|
.dark .ant-table-expanded-row,
|
||||||
|
.dark .ant-table-expanded-row:hover,
|
||||||
|
.dark .ant-table-expanded-row .ant-table-tbody,
|
||||||
|
.dark .ant-calendar {
|
||||||
|
background-color: #101828;
|
||||||
|
color: rgb(255 255 255 /65%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-table-row-expand-icon,
|
.dark .ant-table-expanded-row .ant-table-thead>tr:first-child>th {
|
||||||
.ant-card-dark .ant-checkbox-inner {
|
border-radius: 0;
|
||||||
background: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-switch-checked {
|
.dark .ant-calendar,
|
||||||
background-color: #0c61b0;
|
.dark .ant-card-bordered {
|
||||||
|
border-color: #151f31;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-btn,
|
.dark .ant-table-bordered,
|
||||||
.ant-card-dark .ant-radio-button-wrapper {
|
.dark .ant-table-bordered.ant-table-empty .ant-table-placeholder,
|
||||||
color: hsla(0,0%,100%,.65);
|
.dark .ant-table-bordered .ant-table-body>table,
|
||||||
background: none;
|
.dark .ant-table-bordered .ant-table-fixed-left table,
|
||||||
border: 1px solid hsla(0,0%,100%,.65);
|
.dark .ant-table-bordered .ant-table-fixed-right table,
|
||||||
|
.dark .ant-table-bordered .ant-table-header>table,
|
||||||
|
.dark .ant-table-bordered .ant-table-thead>tr:not(:last-child)>th,
|
||||||
|
.dark .ant-table-bordered .ant-table-tbody>tr>td,
|
||||||
|
.dark .ant-table-bordered .ant-table-thead>tr>th {
|
||||||
|
border-color: #2C3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-radio-button-wrapper:hover {
|
.dark .ant-table-tbody>tr>td,
|
||||||
color: #177ddc;
|
.dark .ant-table-thead>tr>th,
|
||||||
|
.dark .ant-card-head,
|
||||||
|
.dark .ant-modal-header,
|
||||||
|
.dark .ant-collapse>.ant-collapse-item,
|
||||||
|
.dark .ant-tabs-bar,
|
||||||
|
.dark .ant-list-split .ant-list-item,
|
||||||
|
.dark .ant-popover-title,
|
||||||
|
.dark .ant-calendar-header,
|
||||||
|
.dark .ant-calendar-input-wrap {
|
||||||
|
border-bottom-color: #2C3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-btn-primary {
|
.dark .ant-modal-footer,
|
||||||
color: hsla(0,0%,100%,.65);
|
.dark .ant-collapse-content,
|
||||||
background-color: #073763;
|
.dark .ant-calendar-footer,
|
||||||
border-color: #1890ff;
|
.dark .ant-divider-horizontal.ant-divider-with-text-center:before,
|
||||||
text-shadow: 0 -1px 0 rgba(255,255,255,.12);
|
.dark .ant-divider-horizontal.ant-divider-with-text-center:after {
|
||||||
box-shadow: 0 2px 0 rgba(255,255,255,.045);
|
border-top-color: #2c3950;
|
||||||
}
|
|
||||||
.ant-card-dark .ant-btn-primary:hover {
|
|
||||||
background-color: #40a9ff;
|
|
||||||
border-color: #40a9ff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dark .ant-popover-content {
|
.dark .ant-progress-text,
|
||||||
border: 1px solid #e8e8e8;
|
.dark .ant-card-head,
|
||||||
border-radius: 4px;
|
.dark .ant-form,
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
.dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,
|
||||||
|
.dark .ant-form-item i,
|
||||||
|
.dark .ant-modal-close-x,
|
||||||
|
.dark .ant-pagination-item a,
|
||||||
|
.dark li:not(.ant-pagination-disabled) i,
|
||||||
|
.dark .ant-form .anticon,
|
||||||
|
.dark .ant-tabs-tab-arrow-show:not(.ant-tabs-tab-btn-disabled),
|
||||||
|
.dark .anticon-close,
|
||||||
|
.dark .ant-list-item-meta-title,
|
||||||
|
.dark .ant-select-selection i,
|
||||||
|
.dark .ant-modal-confirm-title,
|
||||||
|
.dark .ant-modal-confirm-content,
|
||||||
|
.dark .ant-popover-message,
|
||||||
|
.dark .ant-modal,
|
||||||
|
.dark .ant-divider-inner-text,
|
||||||
|
.dark .ant-popover-title,
|
||||||
|
.dark .ant-popover-inner-content,
|
||||||
|
.dark h2 {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dark .ant-popover-inner {
|
.dark .ant-list-item-meta-description {
|
||||||
background: #222a37;
|
color: rgb(255 255 255 / 45%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dark .ant-popover-title,
|
.dark .ant-pagination-disabled i,
|
||||||
.ant-dark .ant-popover-inner-content {
|
.dark .ant-tabs-tab-btn-disabled {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: rgb(255 255 255 / 25%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dark .ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow {
|
.dark .ant-input,
|
||||||
border-color: transparent #2e3b52 #2e3b52 transparent;
|
.dark .ant-input-group-addon,
|
||||||
|
.dark .ant-collapse,
|
||||||
|
.dark .ant-select-selection,
|
||||||
|
.dark .ant-input-number,
|
||||||
|
.dark .ant-input-number-handler-wrap,
|
||||||
|
.dark .ant-pagination-item-active,
|
||||||
|
.dark .ant-table-placeholder,
|
||||||
|
.dark .ant-empty-normal,
|
||||||
|
.dark.ant-select-dropdown,
|
||||||
|
.dark .ant-select-dropdown,
|
||||||
|
.dark .ant-select-dropdown li,
|
||||||
|
.dark .ant-select-dropdown-menu-item,
|
||||||
|
.dark .ant-divider:not(.ant-divider-with-text-center),
|
||||||
|
.dark .ant-calendar-input,
|
||||||
|
.dark .client-table-header,
|
||||||
|
.dark .ant-select-selection--multiple .ant-select-selection__choice,
|
||||||
|
.dark .ant-calendar-time-picker-inner {
|
||||||
|
background-color: #222D42;
|
||||||
|
border-color: #2c3950;
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-selection:hover,
|
||||||
|
.dark .ant-calendar-picker-clear,
|
||||||
|
.dark .ant-input-number:hover,
|
||||||
|
.dark .ant-input-number:focus,
|
||||||
|
.dark .ant-input:hover,
|
||||||
|
.dark .ant-input:focus {
|
||||||
|
background-color: rgb(14 73 181 / 30%);
|
||||||
|
border-color: #0E49B5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
background-color: rgb(14 73 181 / 30%);
|
||||||
|
border: 1px solid #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-radio-button-wrapper,
|
||||||
|
.dark .ant-radio-button-wrapper:before {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
background-color: rgb(14 73 181 / 30%);
|
||||||
|
border-color: #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger) ,
|
||||||
|
.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: rgb(14 73 181 / 50%);
|
||||||
|
border-color: #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-btn-primary[disabled],
|
||||||
|
.dark .ant-calendar-ok-btn-disabled {
|
||||||
|
color: rgb(255 255 255 / 35%);
|
||||||
|
background-color: #2c3950;
|
||||||
|
border-color: #42516c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
|
||||||
|
.dark .client-table-odd-row {
|
||||||
|
background-color: #122444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-row-expand-icon {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #fff0;
|
||||||
|
border-color: #9ea2a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-row-expand-icon:hover {
|
||||||
|
color: #0e49b5;
|
||||||
|
background-color: #fff0;
|
||||||
|
border-color: #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-switch:not(.ant-switch-checked) {
|
||||||
|
background-color: #2C3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-progress-line .ant-progress-inner {
|
||||||
|
background-color: #2c3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-progress-circle-trail {
|
||||||
|
stroke: #2c3950 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark,
|
||||||
|
.dark .ant-popover-inner {
|
||||||
|
background-color: #222D42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark>.ant-popover-content>.ant-popover-arrow {
|
||||||
|
border-color: #222D42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,
|
||||||
|
.dark .ant-select-dropdown-menu-item-selected,
|
||||||
|
.dark .ant-select-dropdown-menu-item:hover,
|
||||||
|
.dark .ant-calendar-time-picker-select-option-selected {
|
||||||
|
background-color: #313f5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-dark .ant-menu-item:hover {
|
||||||
|
background-color: #2c3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-alert-message {
|
||||||
|
color: rgb(255 255 255 /85%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
background-color: #ffffff0a;
|
||||||
|
border-color: #344461;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-blue {
|
||||||
|
background-color: #111a2c;
|
||||||
|
border-color: #0f367e;
|
||||||
|
color: #3c89e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-red,
|
||||||
|
.dark .ant-alert-error {
|
||||||
|
background-color: #291515;
|
||||||
|
border-color: #5C2626;
|
||||||
|
color: #e04141;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-orange,
|
||||||
|
.dark .ant-alert-warning {
|
||||||
|
background-color: #312313;
|
||||||
|
border-color: #593914;
|
||||||
|
color: #ffa031;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-green {
|
||||||
|
background-color: #142429;
|
||||||
|
border-color: #23432c;
|
||||||
|
color: #61bf39;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-purple {
|
||||||
|
background-color: #2c1e32;
|
||||||
|
border-color: #49394e;
|
||||||
|
color: #f2eaf1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-content,
|
||||||
|
.dark .ant-modal-header {
|
||||||
|
background-color: #181f2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-title,
|
||||||
|
.dark .ant-form-item-label>label,
|
||||||
|
.dark .ant-checkbox-wrapper,
|
||||||
|
.dark .ant-form-item,
|
||||||
|
.dark .ant-calendar-footer .ant-calendar-today-btn,
|
||||||
|
.dark .ant-calendar-footer .ant-calendar-time-picker-btn,
|
||||||
|
.dark .ant-calendar-day-select,
|
||||||
|
.dark .ant-calendar-month-select,
|
||||||
|
.dark .ant-calendar-year-select,
|
||||||
|
.dark .ant-calendar-date {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
||||||
|
.dark .ant-calendar-last-month-cell .ant-calendar-date {
|
||||||
|
color: #2c3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
|
background-color: #0e49b5 !important;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-date:hover,
|
||||||
|
.dark .ant-calendar-time-picker-select li:hover {
|
||||||
|
background-color: #313f5a;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-header a:hover,
|
||||||
|
.dark .ant-calendar-header a:hover::before,
|
||||||
|
.dark .ant-calendar-header a:hover::after {
|
||||||
|
border-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-time-picker-select li:focus {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
outline: none;
|
||||||
|
background-color: #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-time-picker-select {
|
||||||
|
border-right-color: #2C3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .anticon-close-circle {
|
||||||
|
color: #E04141;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-spin-nested-loading>div>.ant-spin .ant-spin-text {
|
||||||
|
text-shadow: 0 1px 2px #00000077;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-spin {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-spin-dot-item {
|
||||||
|
background-color: #ffffffff;
|
||||||
}
|
}
|
||||||
@@ -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() {
|
get genInboundLinks() {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genInboundLinks(this.address, this.remark);
|
return inbound.genInboundLinks(this.remark);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
897
web/assets/js/model/outbound.js
Normal file
897
web/assets/js/model/outbound.js
Normal file
@@ -0,0 +1,897 @@
|
|||||||
|
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;
|
||||||
|
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) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.id = id;
|
||||||
|
this.flow = flow;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
vnext: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: [{id: this.id, flow: this.flow}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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={}) {
|
||||||
|
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}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
45
web/assets/js/model/setting.js
Normal file
45
web/assets/js/model/setting.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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.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,55 @@ 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='', 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:
|
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
||||||
case Protocols.SHADOWSOCKS:
|
let r = [remark, email].filter(x => x.length > 0).join('-');
|
||||||
JSON.parse(this.settings).clients.forEach((client,index) => {
|
result.push({
|
||||||
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
remark: r,
|
||||||
this.stream.tls.settings.domains.forEach((domain) => {
|
link: this.genLink(addr, port, 'same', r, client)
|
||||||
link += this.genLink(domain.domain, [remark, client.email, domain.remark].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
this.stream.externalProxy.forEach((ep) => {
|
||||||
link += this.genLink(address, [remark, client.email].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
let r = [remark, email, ep.remark].filter(x => x.length > 0).join('-')
|
||||||
}
|
result.push({
|
||||||
|
remark: r,
|
||||||
|
link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client)
|
||||||
});
|
});
|
||||||
return link;
|
});
|
||||||
default: return '';
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
genInboundLinks(remark = '') {
|
||||||
|
if(this.clients){
|
||||||
|
let links = [];
|
||||||
|
this.clients.forEach((client) => {
|
||||||
|
this.genAllLinks(remark,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 +1405,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 +1497,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 +1506,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 +1518,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() {
|
||||||
@@ -1665,7 +1587,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 +1597,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 +1610,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1786,7 +1710,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 +1719,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 +1731,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 +1744,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1922,7 +1849,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 +1859,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 +1872,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 +1886,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";
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total - trafficDiff:
|
||||||
|
return "#0e49b5";
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total:
|
||||||
|
return "#ffa031";
|
||||||
|
default:
|
||||||
|
return "#e04141";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function userExpiryColor(threshold, client, isDark = false) {
|
||||||
|
if (!client.enable) {
|
||||||
|
return isDark ? '#2c3950' : '#bcbcbc';
|
||||||
|
}
|
||||||
|
now = new Date().getTime(),
|
||||||
|
expiry = client.expiryTime;
|
||||||
|
switch (true) {
|
||||||
|
case expiry === null:
|
||||||
|
return "#389e0d";
|
||||||
|
case expiry < 0:
|
||||||
|
return "#0e49b5";
|
||||||
|
case expiry == 0:
|
||||||
|
return "#7a316f";
|
||||||
|
case now < expiry - threshold:
|
||||||
|
return "#0e49b5";
|
||||||
|
case now < expiry:
|
||||||
|
return "#ffa031";
|
||||||
|
default:
|
||||||
|
return "#e04141";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ 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("/onlines", a.onlines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getInbounds(c *gin.Context) {
|
func (a *InboundController) getInbounds(c *gin.Context) {
|
||||||
@@ -253,3 +253,7 @@ 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) 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
web/controller/xraySetting.go
Normal file
57
web/controller/xraySetting.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XraySettingController struct {
|
||||||
|
XraySettingService service.XraySettingService
|
||||||
|
SettingService service.SettingService
|
||||||
|
InboundService service.InboundService
|
||||||
|
}
|
||||||
|
|
||||||
|
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("/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)
|
||||||
|
}
|
||||||
@@ -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,37 @@ 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"`
|
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||||
TgLang string `json:"tgLang" form:"tgLang"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
SubListen string `json:"subListen" form:"subListen"`
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
SubPort int `json:"subPort" form:"subPort"`
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
SubPath string `json:"subPath" form:"subPath"`
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
SubDomain string `json:"subDomain" form:"subDomain"`
|
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||||
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
|
SubURI string `json:"subURI" form:"subURI"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
@@ -106,13 +95,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, 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">
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
#app {
|
|
||||||
padding-top: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 20px 0 50px 0;
|
margin: 20px 0 50px 0;
|
||||||
@@ -43,23 +38,88 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#login {
|
||||||
|
animation: charge .5s both;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
|
#login:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,.09);
|
||||||
|
}
|
||||||
|
@keyframes charge {
|
||||||
|
from {transform: translateY(5rem);opacity: 0}
|
||||||
|
to {transform: translateY(0);opacity: 1}
|
||||||
|
}
|
||||||
|
@keyframes wave {
|
||||||
|
from {transform: rotate(0deg);}
|
||||||
|
to {transform: rotate(360deg);}
|
||||||
|
}
|
||||||
|
.wave {
|
||||||
|
opacity: .6;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 40%;
|
||||||
|
left: 50%;
|
||||||
|
width: 6000px;
|
||||||
|
height: 6000px;
|
||||||
|
background: #000;
|
||||||
|
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: .3;
|
||||||
|
}
|
||||||
|
.wave3 {
|
||||||
|
animation: wave 80s infinite linear;
|
||||||
|
opacity: .1;
|
||||||
|
}
|
||||||
|
.wave {
|
||||||
|
background: #0e49b515;
|
||||||
|
}
|
||||||
|
.under {
|
||||||
|
background-color: #dce9f5;
|
||||||
|
}
|
||||||
|
.dark .wave {
|
||||||
|
background: rgb(14 73 181 / 20%);
|
||||||
|
}
|
||||||
|
.dark .under {
|
||||||
|
background-color: #101828;
|
||||||
|
}
|
||||||
|
.dark #login {
|
||||||
|
background-color: #151F31;
|
||||||
|
}
|
||||||
|
.dark h1 {
|
||||||
|
color: rgb(255 255 255 / 85%);
|
||||||
|
}
|
||||||
</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">
|
||||||
|
<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%;">
|
||||||
|
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="6" id="login">
|
||||||
<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>
|
||||||
@@ -77,8 +137,8 @@
|
|||||||
</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 +149,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 +169,18 @@
|
|||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "component/password" .}}
|
{{template "component/password" .}}
|
||||||
<script>
|
<script>
|
||||||
|
class User {
|
||||||
|
constructor() {
|
||||||
|
this.username = "";
|
||||||
|
this.password = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const State = {
|
||||||
|
Running: "running",
|
||||||
|
Stop: "stop",
|
||||||
|
Error: "error",
|
||||||
|
}
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -112,7 +191,6 @@
|
|||||||
lang: ""
|
lang: ""
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.updateBackground();
|
|
||||||
this.lang = getLang();
|
this.lang = getLang();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -123,15 +201,7 @@
|
|||||||
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>
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
545
web/html/xui/form/outbound.html
Normal file
545
web/html/xui/form/outbound.html
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
{{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>
|
||||||
|
<a-input v-model.trim="outbound.tag" style="width: 250px" @change="outModal.check()" :style="outModal.duplicateTag? 'border-color: red;' : ''"></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"></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>
|
||||||
|
</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,7 +19,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>
|
||||||
@@ -124,7 +33,7 @@
|
|||||||
|
|
||||||
<!-- 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.delTrojanFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||||
@@ -144,7 +53,7 @@
|
|||||||
<a-form-item label="xver">
|
<a-form-item label="xver">
|
||||||
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
|
||||||
</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,7 +35,7 @@
|
|||||||
|
|
||||||
<!-- 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;"/>
|
||||||
@@ -157,7 +55,7 @@
|
|||||||
<a-form-item label="xver">
|
<a-form-item label="xver">
|
||||||
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
|
||||||
</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">
|
||||||
|
<tr style="line-height: 40px;">
|
||||||
|
<td width="100%">
|
||||||
|
<a-input-group style="margin-top: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>{{ 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="color: blue"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px;" 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" 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>
|
||||||
<a-tag v-else-if="client.expiryTime < 0" color="cyan">[[ client._expiryTime ]] {{ i18n "pages.client.days" }}</a-tag>
|
<template v-else>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<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>
|
||||||
|
<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>
|
</template>
|
||||||
{{end}}
|
{{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, 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,53 +61,69 @@
|
|||||||
</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="export">
|
<a-menu-item key="export">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
@@ -99,11 +144,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 +156,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 +195,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"}}
|
||||||
@@ -183,42 +234,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 +292,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 +305,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="isMobile ? 'margin: -16px -5px -17px;' : 'margin-left: 10px;'">
|
||||||
>
|
|
||||||
{{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 +427,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 +468,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 +478,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 +538,18 @@
|
|||||||
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
|
|
||||||
},
|
},
|
||||||
tgBotEnable: false,
|
tgBotEnable: false,
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
|
pageSize: 0,
|
||||||
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -393,11 +562,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 +588,9 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
@@ -441,8 +616,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 +625,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 +647,7 @@
|
|||||||
deactive: deactive,
|
deactive: deactive,
|
||||||
depleted: depleted,
|
depleted: depleted,
|
||||||
expiring: expiring,
|
expiring: expiring,
|
||||||
|
online: online,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
@@ -547,10 +724,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);
|
||||||
@@ -616,6 +793,7 @@
|
|||||||
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.update"}}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
const baseInbound = dbInbound.toInbound();
|
const baseInbound = dbInbound.toInbound();
|
||||||
@@ -752,7 +930,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: () => {
|
onOk: () => {
|
||||||
@@ -767,31 +945,26 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
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"}}',
|
||||||
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 +978,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.stream.isTls &&
|
||||||
['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 +986,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 +1018,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 +1032,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"}}',
|
||||||
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 +1062,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,36 +1072,89 @@
|
|||||||
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);
|
||||||
@@ -931,11 +1162,11 @@
|
|||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks, newDbInbound.remark);
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks, newDbInbound.remark);
|
||||||
},
|
},
|
||||||
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);
|
||||||
}
|
}
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
|
||||||
},
|
},
|
||||||
async startDataRefreshLoop() {
|
async startDataRefreshLoop() {
|
||||||
while (this.isRefreshEnabled) {
|
while (this.isRefreshEnabled) {
|
||||||
@@ -963,6 +1194,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 +1228,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,28 +83,28 @@
|
|||||||
<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-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</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.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-tooltip v-if="status.xray.state === State.Error">
|
||||||
@@ -114,34 +113,34 @@
|
|||||||
</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="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-tag color="purple" 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,8 +161,8 @@
|
|||||||
</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 ]]
|
{{ i18n "pages.index.connectionCount" }}: TCP: [[ status.tcpCount ]] UDP: [[ status.udpCount ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionCountDesc" }}
|
{{ i18n "pages.index.connectionCountDesc" }}
|
||||||
@@ -173,7 +172,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="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
@@ -199,7 +198,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,12 +230,12 @@
|
|||||||
|
|
||||||
<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>
|
||||||
@@ -245,15 +244,14 @@
|
|||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI 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 +261,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>
|
||||||
@@ -284,17 +281,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 v-model="logModal.logs" class="ant-input" style="height: 400px; overflow: scroll;"><pre>[[ logModal.logs ]]</pre></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 +304,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 +332,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 +379,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";
|
||||||
@@ -487,8 +484,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();
|
||||||
@@ -593,4 +590,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,31 @@
|
|||||||
</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-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<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 +84,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>
|
||||||
@@ -97,9 +100,7 @@
|
|||||||
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,7 +113,7 @@
|
|||||||
</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;">
|
||||||
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
||||||
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -130,172 +131,8 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</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,8 +151,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>
|
||||||
@@ -328,14 +165,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 +176,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 +187,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"}}
|
||||||
@@ -372,62 +205,7 @@
|
|||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
user: {},
|
user: {},
|
||||||
lang: getLang(),
|
lang: getLang(),
|
||||||
showAlert: false,
|
showAlert: false
|
||||||
ipv4Settings: {
|
|
||||||
tag: "IPv4",
|
|
||||||
protocol: "freedom",
|
|
||||||
settings: {
|
|
||||||
domainStrategy: "UseIPv4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directSettings: {
|
|
||||||
tag: "direct",
|
|
||||||
protocol: "freedom"
|
|
||||||
},
|
|
||||||
outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
|
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
|
||||||
settingsData: {
|
|
||||||
protocols: {
|
|
||||||
bittorrent: ["bittorrent"],
|
|
||||||
},
|
|
||||||
ips: {
|
|
||||||
local: ["geoip:private"],
|
|
||||||
cn: ["geoip:cn"],
|
|
||||||
ir: ["geoip:ir"],
|
|
||||||
ru: ["geoip:ru"],
|
|
||||||
},
|
|
||||||
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: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -465,6 +243,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 +255,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 +273,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>
|
||||||
|
|||||||
1227
web/html/xui/xray.html
Normal file
1227
web/html/xui/xray.html
Normal file
File diff suppressed because it is too large
Load Diff
125
web/html/xui/xray_outbound_modal.html
Normal file
125
web/html/xui/xray_outbound_modal.html
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
{{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',
|
||||||
|
ok() {
|
||||||
|
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "sure" }}', outbound, confirm=(outbound)=>{}, isEdit=false }) {
|
||||||
|
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.check()
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
outModal.visible = false;
|
||||||
|
outModal.loading(false);
|
||||||
|
},
|
||||||
|
loading(loading) {
|
||||||
|
outModal.confirmLoading = loading;
|
||||||
|
},
|
||||||
|
check(){
|
||||||
|
if(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.map(obj => obj.tag);
|
||||||
|
this.inboundTags.push(...app.inboundTags);
|
||||||
|
this.outboundTags = app.templateSettings.outbounds.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}}
|
||||||
275
web/html/xui/xray_rule_modal.html
Normal file
275
web/html/xui/xray_rule_modal.html
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
{{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>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: {
|
||||||
|
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.map(obj => obj.tag);
|
||||||
|
this.inboundTags.push(...app.inboundTags);
|
||||||
|
this.outboundTags = app.templateSettings.outbounds.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.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",
|
||||||
|
|||||||
@@ -249,7 +249,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 +301,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 +314,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 +580,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 +647,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 +667,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 +694,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 +728,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 +801,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 +990,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 +999,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 +1127,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 +1194,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 +1278,16 @@ 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
|
||||||
|
}
|
||||||
|
return "[\"" + strings.Join(inboundTags, "\", \"") + "\"]", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrationRequirements() {
|
func (s *InboundService) MigrationRequirements() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
@@ -1242,9 +1357,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
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ 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",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Asia/Tehran",
|
||||||
@@ -52,6 +53,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 +62,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
|
||||||
}
|
}
|
||||||
@@ -382,6 +384,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 +421,61 @@ 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() },
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,10 +75,16 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bot, err = tgbotapi.NewBotAPI(tgBottoken)
|
for {
|
||||||
if err != nil {
|
bot, err = tgbotapi.NewBotAPI(tgBottoken)
|
||||||
fmt.Println("Get tgbot's api error:", err)
|
if err != nil {
|
||||||
return err
|
fmt.Println("Get tgbot's api error:", err)
|
||||||
|
fmt.Println("Retrying after 10 secound...")
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Tgbot connected!")
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bot.Debug = false
|
bot.Debug = false
|
||||||
|
|
||||||
@@ -207,8 +213,14 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
|
|||||||
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
||||||
case "client_commands":
|
case "client_commands":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpClientCommands"))
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpClientCommands"))
|
||||||
|
case "onlines":
|
||||||
|
t.onlineClients(callbackQuery.From.ID)
|
||||||
case "commands":
|
case "commands":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
||||||
|
default:
|
||||||
|
if callbackQuery.Data[:7] == "client_" {
|
||||||
|
t.searchClient(callbackQuery.From.ID, callbackQuery.Data[7:])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +245,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
|||||||
),
|
),
|
||||||
tgbotapi.NewInlineKeyboardRow(
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.commands"), "commands"),
|
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.commands"), "commands"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.onlines"), "onlines"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
numericKeyboardClient := tgbotapi.NewInlineKeyboardMarkup(
|
numericKeyboardClient := tgbotapi.NewInlineKeyboardMarkup(
|
||||||
@@ -429,6 +442,52 @@ func (t *Tgbot) getInboundUsages() string {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic) string {
|
||||||
|
expiryTime := ""
|
||||||
|
if traffic.ExpiryTime == 0 {
|
||||||
|
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||||
|
} else if traffic.ExpiryTime < 0 {
|
||||||
|
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||||
|
} else {
|
||||||
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
total := ""
|
||||||
|
if traffic.Total == 0 {
|
||||||
|
total = t.I18nBot("tgbot.unlimited")
|
||||||
|
} else {
|
||||||
|
total = common.FormatTraffic((traffic.Total))
|
||||||
|
}
|
||||||
|
|
||||||
|
active := ""
|
||||||
|
if traffic.Enable {
|
||||||
|
active = t.I18nBot("tgbot.messages.yes")
|
||||||
|
} else {
|
||||||
|
active = t.I18nBot("tgbot.messages.no")
|
||||||
|
}
|
||||||
|
|
||||||
|
status := "🔴 " + t.I18nBot("offline")
|
||||||
|
if p.IsRunning() {
|
||||||
|
for _, online := range p.GetOnlineClients() {
|
||||||
|
if online == traffic.Email {
|
||||||
|
status = "🟢 " + t.I18nBot("online")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output := ""
|
||||||
|
output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
|
||||||
|
output += t.I18nBot("tgbot.messages.online", "Status=="+status)
|
||||||
|
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||||
|
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||||
|
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||||
|
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||||
|
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
||||||
if len(tgUserName) == 0 {
|
if len(tgUserName) == 0 {
|
||||||
msg := t.I18nBot("tgbot.answers.askToAddUser")
|
msg := t.I18nBot("tgbot.answers.askToAddUser")
|
||||||
@@ -436,7 +495,7 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
|
traffics, err := t.inboundService.GetClientTrafficTgBot(strconv.FormatInt(chatId, 10), tgUserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
msg := t.I18nBot("tgbot.wentWrong")
|
msg := t.I18nBot("tgbot.wentWrong")
|
||||||
@@ -445,35 +504,13 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
|||||||
}
|
}
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
msg := t.I18nBot("tgbot.answers.askToAddUserName", "TgUserName=="+tgUserName)
|
msg := t.I18nBot("tgbot.answers.askToAddUserName", "TgUserName=="+tgUserName)
|
||||||
|
msg += "\r\n" + t.I18nBot("tgbot.commands.getID", "ID=="+strconv.FormatInt(chatId, 10))
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
expiryTime := ""
|
output := t.clientInfoMsg(traffic)
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ""
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
t.SendAnswer(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), false)
|
t.SendAnswer(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), false)
|
||||||
@@ -493,30 +530,7 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expiryTime := ""
|
output := t.clientInfoMsg(traffic)
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ""
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,30 +563,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|||||||
t.SendMsgToTgbot(chatId, info)
|
t.SendMsgToTgbot(chatId, info)
|
||||||
|
|
||||||
for _, traffic := range inbound.ClientStats {
|
for _, traffic := range inbound.ClientStats {
|
||||||
expiryTime := ""
|
output := t.clientInfoMsg(&traffic)
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ""
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,30 +583,7 @@ func (t *Tgbot) searchForClient(chatId int64, query string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expiryTime := ""
|
output := t.clientInfoMsg(traffic)
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ""
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,28 +665,7 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
output += t.I18nBot("tgbot.messages.exhaustedMsg", "Type=="+t.I18nBot("tgbot.clients"))
|
output += t.I18nBot("tgbot.messages.exhaustedMsg", "Type=="+t.I18nBot("tgbot.clients"))
|
||||||
|
|
||||||
for _, traffic := range exhaustedClients {
|
for _, traffic := range exhaustedClients {
|
||||||
expiryTime := ""
|
output += t.clientInfoMsg(&traffic)
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime += fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
output += "\r\n \r\n"
|
output += "\r\n \r\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -726,6 +673,25 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) onlineClients(chatId int64) {
|
||||||
|
if !p.IsRunning() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onlines := p.GetOnlineClients()
|
||||||
|
output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(len(onlines)))
|
||||||
|
if len(onlines) > 0 {
|
||||||
|
keyboard := tgbotapi.NewInlineKeyboardMarkup()
|
||||||
|
for index, online := range onlines {
|
||||||
|
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("%d: %s\r\n", index+1, online), "client_"+online)))
|
||||||
|
}
|
||||||
|
t.SendMsgToTgbot(chatId, output, keyboard)
|
||||||
|
} else {
|
||||||
|
t.SendMsgToTgbot(chatId, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tgbot) sendBackup(chatId int64) {
|
func (t *Tgbot) sendBackup(chatId int64) {
|
||||||
if !t.IsRunning() {
|
if !t.IsRunning() {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -132,6 +132,32 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
|
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(inbound.StreamSettings) > 0 {
|
||||||
|
// Unmarshal stream JSON
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
|
||||||
|
// Remove the "settings" field under "tlsSettings" and "realitySettings"
|
||||||
|
tlsSettings, ok1 := stream["tlsSettings"].(map[string]interface{})
|
||||||
|
realitySettings, ok2 := stream["realitySettings"].(map[string]interface{})
|
||||||
|
if ok1 || ok2 {
|
||||||
|
if ok1 {
|
||||||
|
delete(tlsSettings, "settings")
|
||||||
|
} else if ok2 {
|
||||||
|
delete(realitySettings, "settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(stream, "externalProxy")
|
||||||
|
|
||||||
|
newStream, err := json.MarshalIndent(stream, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inbound.StreamSettings = string(newStream)
|
||||||
|
}
|
||||||
|
|
||||||
inboundConfig := inbound.GenXrayInboundConfig()
|
inboundConfig := inbound.GenXrayInboundConfig()
|
||||||
xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig)
|
xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig)
|
||||||
}
|
}
|
||||||
|
|||||||
28
web/service/xraySettings.go
Normal file
28
web/service/xraySettings.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/xray"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XraySettingService struct {
|
||||||
|
SettingService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XraySettingService) SaveXraySetting(newXraySettings string) error {
|
||||||
|
if err := s.CheckXrayConfig(newXraySettings); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.SettingService.saveSetting("xrayTemplateConfig", newXraySettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
||||||
|
xrayConfig := &xray.Config{}
|
||||||
|
err := json.Unmarshal([]byte(XrayTemplateConfig), xrayConfig)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("xray template config invalid:", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user