mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-20 07:45:48 +00:00
Compare commits
124 Commits
0.4.1
...
1.0.0_beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
772a1b341e | ||
|
|
5923c765a9 | ||
|
|
d7074cc3c9 | ||
|
|
f4f8c0d6df | ||
|
|
0c755be648 | ||
|
|
c8dc4a96b4 | ||
|
|
8d9f6ccc11 | ||
|
|
3d7a8e372e | ||
|
|
2fe4f1ad07 | ||
|
|
04095b6ec4 | ||
|
|
6d404e4b27 | ||
|
|
f9f8431dd2 | ||
|
|
f996b9ee2b | ||
|
|
a3e1ee1f35 | ||
|
|
e16391ad05 | ||
|
|
48b543becb | ||
|
|
80f052697c | ||
|
|
fb15248030 | ||
|
|
6a18ba48aa | ||
|
|
c13b7b2a18 | ||
|
|
838bdaf314 | ||
|
|
f6f0cf3657 | ||
|
|
dd5e9a97a6 | ||
|
|
edfbb91dff | ||
|
|
eb239b33af | ||
|
|
4b9940fb06 | ||
|
|
98d5e960a4 | ||
|
|
61fd029e28 | ||
|
|
2be3df59be | ||
|
|
7cf192e179 | ||
|
|
3a002c886d | ||
|
|
4b05c333ca | ||
|
|
bfc5b37bb1 | ||
|
|
07089455b5 | ||
|
|
7add969312 | ||
|
|
501f778c9e | ||
|
|
ac52340c51 | ||
|
|
f5f90d81a3 | ||
|
|
e6c23caa1d | ||
|
|
bf3a64c77d | ||
|
|
e2e88d8652 | ||
|
|
91b453fff7 | ||
|
|
9e955df76a | ||
|
|
58ca5f7a51 | ||
|
|
4f469f9ad5 | ||
|
|
1969120c94 | ||
|
|
cdbf742b66 | ||
|
|
02a4f0c7d4 | ||
|
|
e0a64a8257 | ||
|
|
38db94f507 | ||
|
|
d0ce67a8f0 | ||
|
|
fcc3f67181 | ||
|
|
357630b077 | ||
|
|
1246b54ffa | ||
|
|
2e3119ab9b | ||
|
|
b834ac5d93 | ||
|
|
92df5659c9 | ||
|
|
f85bd39a0a | ||
|
|
67fe79a7eb | ||
|
|
5cd3ee10de | ||
|
|
e3e7b0f736 | ||
|
|
19280cdfbd | ||
|
|
17a483266e | ||
|
|
1e7d2010a8 | ||
|
|
97a0e2cae9 | ||
|
|
e9c3c330f2 | ||
|
|
294894eca0 | ||
|
|
f3df93a950 | ||
|
|
1780f06242 | ||
|
|
50671bfce3 | ||
|
|
7332258dce | ||
|
|
d9523cfd37 | ||
|
|
47f75f6382 | ||
|
|
a6f2dc577a | ||
|
|
36ddde3491 | ||
|
|
f1a87c7d92 | ||
|
|
48f7b88ede | ||
|
|
b7048cdab8 | ||
|
|
df2ef6cdde | ||
|
|
efd1b06593 | ||
|
|
28050aba23 | ||
|
|
27de7b030b | ||
|
|
9ae49ed562 | ||
|
|
cee117e1e8 | ||
|
|
8f0a701d9e | ||
|
|
921a72b5cf | ||
|
|
9e902fc82f | ||
|
|
6ed8ff8e05 | ||
|
|
f333ad23d9 | ||
|
|
3f49aa5541 | ||
|
|
0f487dc10f | ||
|
|
f3c0edac07 | ||
|
|
394c857bb6 | ||
|
|
6080709fc7 | ||
|
|
17c47f0711 | ||
|
|
dac234357a | ||
|
|
2d15073a90 | ||
|
|
1b2e165a65 | ||
|
|
e1f8e8efd2 | ||
|
|
738c7064a9 | ||
|
|
c24483ad44 | ||
|
|
046f071378 | ||
|
|
d40a16e29d | ||
|
|
2f1f28cc0b | ||
|
|
70ff51d629 | ||
|
|
7472c31c34 | ||
|
|
04a8d5c177 | ||
|
|
7b2bd4247d | ||
|
|
5aee1c886b | ||
|
|
7a9a30a038 | ||
|
|
eb17cec289 | ||
|
|
22e107c746 | ||
|
|
36a93e2959 | ||
|
|
073247f054 | ||
|
|
96c8932961 | ||
|
|
5fac7df174 | ||
|
|
8bba9f3c25 | ||
|
|
268e8c7eb1 | ||
|
|
14e8f6cbfa | ||
|
|
7f0ba495db | ||
|
|
f829213dd1 | ||
|
|
4d5a73512e | ||
|
|
6dccc5b891 | ||
|
|
f642830dc8 |
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -8,13 +8,13 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
linuxamd64build:
|
linuxamd64build:
|
||||||
name: build x-ui amd64 version
|
name: build x-ui amd64 version
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: 'stable'
|
||||||
- name: build linux amd64 version
|
- name: build linux amd64 version
|
||||||
run: |
|
run: |
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
||||||
@@ -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/latest/download/Xray-linux-64.zip
|
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
linuxarm64build:
|
linuxarm64build:
|
||||||
name: build x-ui arm64 version
|
name: build x-ui arm64 version
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
@@ -66,7 +66,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/latest/download/Xray-linux-arm64-v8a.zip
|
wget https://github.com/xtls/xray-core/releases/download/v1.8.1/Xray-linux-arm64-v8a.zip
|
||||||
unzip Xray-linux-arm64-v8a.zip
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
|
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
linuxs390xbuild:
|
linuxs390xbuild:
|
||||||
name: build x-ui s390x version
|
name: build x-ui s390x version
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
@@ -106,7 +106,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/latest/download/Xray-linux-s390x.zip
|
wget https://github.com/xtls/xray-core/releases/download/v1.8.1/Xray-linux-s390x.zip
|
||||||
unzip Xray-linux-s390x.zip
|
unzip Xray-linux-s390x.zip
|
||||||
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat
|
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -123,4 +123,4 @@ jobs:
|
|||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-s390x.tar.gz
|
file: x-ui-linux-s390x.tar.gz
|
||||||
asset_name: x-ui-linux-s390x.tar.gz
|
asset_name: x-ui-linux-s390x.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ ENV TZ=Asia/Tehran
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add ca-certificates tzdata && mkdir bin
|
RUN apk add ca-certificates tzdata && mkdir bin
|
||||||
|
|
||||||
|
# Download latest rule files
|
||||||
|
ADD https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat \
|
||||||
|
https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat \
|
||||||
|
bin/
|
||||||
|
|
||||||
COPY --from=builder /app/main /app/x-ui
|
COPY --from=builder /app/main /app/x-ui
|
||||||
VOLUME [ "/etc/x-ui" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
CMD [ "./x-ui" ]
|
CMD [ "./x-ui" ]
|
||||||
121
README.md
121
README.md
@@ -1,25 +1,29 @@
|
|||||||
# x-ui
|
# x-ui
|
||||||

|
|
||||||
|

|
||||||

|

|
||||||
[](https://goreportcard.com/report/github.com/alireza0/x-ui)
|
[](https://goreportcard.com/report/github.com/alireza0/x-ui)
|
||||||
[](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)
|
[](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
||||||
|
|
||||||
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
||||||
|
|
||||||
| Features | Enable? |
|
| Features | Enable? |
|
||||||
| ------------- |:-------------:|
|
| ------------------------------------ | :----------------: |
|
||||||
| Multi-lang | :heavy_check_mark: |
|
| Multi-lang | :heavy_check_mark: |
|
||||||
| Dark/Light Theme | :heavy_check_mark: |
|
| Dark/Light Theme | :heavy_check_mark: |
|
||||||
| Search in deep | :heavy_check_mark: |
|
| Search in deep | :heavy_check_mark: |
|
||||||
| Inbound Multi User | :heavy_check_mark: |
|
| Inbound Multi User | :heavy_check_mark: |
|
||||||
| Multi User Traffic & Expiration time | :heavy_check_mark: |
|
| Multi User Traffic & Expiration time | :heavy_check_mark: |
|
||||||
| REST API | :heavy_check_mark: |
|
| REST API | :heavy_check_mark: |
|
||||||
| Telegram BOT (admin + clients) | :heavy_check_mark: |
|
| Telegram BOT (admin + clients) | :heavy_check_mark: |
|
||||||
| Backup database using Telegram BOT | :heavy_check_mark: |
|
| Backup database using Telegram BOT | :heavy_check_mark: |
|
||||||
|
| Subscription link | :heavy_check_mark: |
|
||||||
|
| Calculate expire date on first usage | :heavy_check_mark: |
|
||||||
|
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
@@ -31,21 +35,64 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
|||||||
- Support for configuring more transport configurations
|
- Support for configuring more transport configurations
|
||||||
- Traffic statistics, limit traffic, limit expiration time
|
- Traffic statistics, limit traffic, limit expiration time
|
||||||
- Customizable xray configuration templates
|
- Customizable xray configuration templates
|
||||||
|
- Support subscription ( multi ) link
|
||||||
|
- Detect users which are expiring or exceed traffic limit soon
|
||||||
- Support https access panel (self-provided domain name + ssl certificate)
|
- Support https access panel (self-provided domain name + ssl certificate)
|
||||||
- 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
|
||||||
|
|
||||||
|
## API routes
|
||||||
|
|
||||||
|
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
||||||
|
- `/xui/API/inbounds` base for following actions:
|
||||||
|
|
||||||
|
| Method | Path | Action |
|
||||||
|
| ------ | -------------------------------- | ------------------------------------------- |
|
||||||
|
| GET | "/" | Get all inbounds |
|
||||||
|
| GET | "/get/:id" | Get inbound with inbound.id |
|
||||||
|
| POST | "/add" | Add inbound |
|
||||||
|
| POST | "/del/:id" | Delete Inbound |
|
||||||
|
| POST | "/update/:id" | Update Inbound |
|
||||||
|
| POST | "/addClient/" | Add Client to inbound |
|
||||||
|
| POST | "/delClient/:email" | Delete Client |
|
||||||
|
| POST | "/updateClient/:index" | Update Client |
|
||||||
|
| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic |
|
||||||
|
| POST | "/resetAllTraffics" | Reset traffics of all inbounds |
|
||||||
|
| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound |
|
||||||
|
|
||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
| Variable | Type | Default |
|
||||||
|
| -------------- | :--------------------------------------------: | :------------ |
|
||||||
|
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||||
|
| XUI_DEBUG | `boolean` | `false` |
|
||||||
|
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||||
|
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||||
|
|
||||||
# Screenshot from Inbouds page
|
# Screenshot from Inbouds page
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
# Install & Upgrade
|
## suggestion system
|
||||||
|
|
||||||
|
- CentOS 8+
|
||||||
|
- Ubuntu 20+
|
||||||
|
- Debian 8+
|
||||||
|
- Fedora 36+
|
||||||
|
|
||||||
|
# Install & Upgrade to latest version
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Install custom version
|
||||||
|
To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
|
||||||
|
```
|
||||||
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
|
||||||
|
```
|
||||||
|
|
||||||
## Manual install & upgrade
|
## Manual install & upgrade
|
||||||
|
|
||||||
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases , generally choose Architecture `amd64`
|
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases , generally choose Architecture `amd64`
|
||||||
@@ -95,9 +142,8 @@ docker build -t x-ui .
|
|||||||
|
|
||||||
## SSL certificate application
|
## SSL certificate application
|
||||||
|
|
||||||
### Cloudflare
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
|
||||||
|
|
||||||
### Certbot
|
### Certbot
|
||||||
|
|
||||||
@@ -109,9 +155,12 @@ ln -s /snap/bin/certbot /usr/bin/certbot
|
|||||||
certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d <Your Domain Name>
|
certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d <Your Domain Name>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Tg robot use
|
## Tg robot use
|
||||||
|
|
||||||
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
||||||
Set the robot-related parameters in the panel background, including:
|
Set the robot-related parameters in the panel background, including:
|
||||||
@@ -126,7 +175,8 @@ Set the robot-related parameters in the panel background, including:
|
|||||||
|
|
||||||
Reference syntax:
|
Reference syntax:
|
||||||
|
|
||||||
- 30 * * * * * //Notify at the 30s of each point
|
- 30 \* \* \* \* \* //Notify at the 30s of each point
|
||||||
|
- 0 */10 \* \* \* \* //Notify at the first second of each 10 minutes
|
||||||
- @hourly // hourly notification
|
- @hourly // hourly notification
|
||||||
- @daily // Daily notification (00:00 in the morning)
|
- @daily // Daily notification (00:00 in the morning)
|
||||||
- @every 8h // notify every 8 hours
|
- @every 8h // notify every 8 hours
|
||||||
@@ -137,25 +187,20 @@ 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 if client's telegram username is added to the end of `email` like 'test123@telegram_username'
|
- Support client report menu if client's telegram username added to the user's configurations
|
||||||
- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
||||||
- Menu based bot
|
- Menu based bot
|
||||||
- Search client by email ( only admin )
|
- Search client by email ( only admin )
|
||||||
- Check all inbounds
|
- Check all inbounds
|
||||||
- Check server status
|
- Check server status
|
||||||
- Check Exhausted users
|
- Check depleted users
|
||||||
- Receive backup by request and in periodic reports
|
- Receive backup by request and in periodic reports
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Common problem
|
||||||
|
|
||||||
|
<details>
|
||||||
## suggestion system
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
- CentOS 7+
|
|
||||||
- Ubuntu 16+
|
|
||||||
- Debian 8+
|
|
||||||
|
|
||||||
# common problem
|
|
||||||
|
|
||||||
## Migrating from v2-ui
|
## Migrating from v2-ui
|
||||||
|
|
||||||
First install the latest version of x-ui on the server where v2-ui is installed, and then use the following command to migrate, which will migrate the native v2-ui `All inbound account data` to x-ui,`Panel settings and username passwords are not migrated`
|
First install the latest version of x-ui on the server where v2-ui is installed, and then use the following command to migrate, which will migrate the native v2-ui `All inbound account data` to x-ui,`Panel settings and username passwords are not migrated`
|
||||||
@@ -170,12 +215,15 @@ x-ui v2-ui
|
|||||||
|
|
||||||
**If you upgrade from an old version or other forks, for enable traffic for users you should do :**
|
**If you upgrade from an old version or other forks, for enable traffic for users you should do :**
|
||||||
|
|
||||||
find this in config :
|
find this in config :
|
||||||
``` json
|
|
||||||
|
```json
|
||||||
"policy": {
|
"policy": {
|
||||||
"system": {
|
"system": {
|
||||||
```
|
```
|
||||||
**and add this just after ` "policy": {` :**
|
|
||||||
|
**and add this just after ` "policy": {` :**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"levels": {
|
"levels": {
|
||||||
"0": {
|
"0": {
|
||||||
@@ -185,8 +233,8 @@ find this in config :
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
**the final output is like :**
|
**the final output is like :**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"policy": {
|
"policy": {
|
||||||
"levels": {
|
"levels": {
|
||||||
@@ -203,12 +251,15 @@ find this in config :
|
|||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
```
|
```
|
||||||
restart panel
|
|
||||||
|
restart panel
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
# a special thanks to
|
# a special thanks to
|
||||||
|
|
||||||
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
||||||
- [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
- [MHSanaei](https://github.com/MHSanaei)
|
||||||
- [MHSanaei](https://github.com/MHSanaei)
|
|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over time
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,22 @@ func IsDebug() bool {
|
|||||||
return os.Getenv("XUI_DEBUG") == "true"
|
return os.Getenv("XUI_DEBUG") == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDBPath() string {
|
func GetBinFolderPath() string {
|
||||||
return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName())
|
binFolderPath := os.Getenv("XUI_BIN_FOLDER")
|
||||||
|
if binFolderPath == "" {
|
||||||
|
binFolderPath = "bin"
|
||||||
|
}
|
||||||
|
return binFolderPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDBFolderPath() string {
|
||||||
|
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||||
|
if dbFolderPath == "" {
|
||||||
|
dbFolderPath = "/etc/x-ui"
|
||||||
|
}
|
||||||
|
return dbFolderPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDBPath() string {
|
||||||
|
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.4.1
|
1.0.0_beta1
|
||||||
@@ -74,4 +74,7 @@ type Client struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
|
Enable bool `json:"enable" form:"enable"`
|
||||||
|
TgID string `json:"tgId" form:"tgId"`
|
||||||
|
SubID string `json:"subId" form:"subId"`
|
||||||
}
|
}
|
||||||
|
|||||||
13
go.mod
13
go.mod
@@ -7,17 +7,18 @@ require (
|
|||||||
github.com/gin-contrib/sessions v0.0.4
|
github.com/gin-contrib/sessions v0.0.4
|
||||||
github.com/gin-gonic/gin v1.9.0
|
github.com/gin-gonic/gin v1.9.0
|
||||||
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/nicksnyder/go-i18n/v2 v2.2.1
|
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7
|
github.com/pelletier/go-toml/v2 v2.0.7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.2
|
github.com/shirou/gopsutil/v3 v3.23.3
|
||||||
github.com/xtls/xray-core v1.8.0
|
github.com/xtls/xray-core v1.8.0
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.10.0
|
||||||
golang.org/x/text v0.8.0
|
golang.org/x/text v0.9.0
|
||||||
google.golang.org/grpc v1.53.0
|
google.golang.org/grpc v1.54.0
|
||||||
gorm.io/driver/sqlite v1.4.4
|
gorm.io/driver/sqlite v1.5.0
|
||||||
gorm.io/gorm v1.24.6
|
gorm.io/gorm v1.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -29,7 +30,6 @@ require (
|
|||||||
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.11.2 // indirect
|
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||||
github.com/goccy/go-json v0.10.0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // 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
|
||||||
@@ -46,6 +46,7 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.4 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -44,8 +44,8 @@ github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVL
|
|||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||||
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.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
@@ -70,7 +70,6 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
|
|||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
@@ -132,8 +131,12 @@ github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08=
|
|||||||
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
|
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU=
|
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M=
|
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.4 h1:SZPIgRM2sEF9NJy50mRHu9PKGwxyyTTJIWvCtgVbozs=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||||
|
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||||
|
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -205,7 +208,6 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -215,8 +217,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -229,8 +231,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||||
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.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=
|
google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=
|
||||||
@@ -244,11 +246,11 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||||||
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.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.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
||||||
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
||||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
|
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||||
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
72
install.sh
72
install.sh
@@ -8,26 +8,20 @@ plain='\033[0m'
|
|||||||
cur_dir=$(pwd)
|
cur_dir=$(pwd)
|
||||||
|
|
||||||
# check root
|
# check root
|
||||||
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error:${plain} Please run this script with root privilege \n " && exit 1
|
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
|
||||||
|
|
||||||
# check os
|
# Check OS and set release variable
|
||||||
if [[ -f /etc/redhat-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
release="centos"
|
source /etc/os-release
|
||||||
elif cat /etc/issue | grep -Eqi "debian"; then
|
release=$ID
|
||||||
release="debian"
|
elif [[ -f /usr/lib/os-release ]]; then
|
||||||
elif cat /etc/issue | grep -Eqi "ubuntu"; then
|
source /usr/lib/os-release
|
||||||
release="ubuntu"
|
release=$ID
|
||||||
elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then
|
|
||||||
release="centos"
|
|
||||||
elif cat /proc/version | grep -Eqi "debian"; then
|
|
||||||
release="debian"
|
|
||||||
elif cat /proc/version | grep -Eqi "ubuntu"; then
|
|
||||||
release="ubuntu"
|
|
||||||
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
|
|
||||||
release="centos"
|
|
||||||
else
|
else
|
||||||
echo -e "${red} Check system OS failed, please contact the author! ${plain}\n" && exit 1
|
echo "Failed to check the system OS, please contact the author!" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
echo "The OS release is: $release"
|
||||||
|
|
||||||
arch=$(arch)
|
arch=$(arch)
|
||||||
|
|
||||||
@@ -39,42 +33,44 @@ elif [[ $arch == "s390x" ]]; then
|
|||||||
arch="s390x"
|
arch="s390x"
|
||||||
else
|
else
|
||||||
arch="amd64"
|
arch="amd64"
|
||||||
echo -e "${red} Fail to check system arch, will use default arch: ${arch}${plain}"
|
echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "arch: ${arch}"
|
echo "arch: ${arch}"
|
||||||
|
|
||||||
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
|
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
|
||||||
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead,if there is something wrong, plz let me know"
|
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
|
||||||
exit -1
|
exit -1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
os_version=""
|
os_version=""
|
||||||
|
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
||||||
|
|
||||||
# os version
|
if [[ "${release}" == "centos" ]]; then
|
||||||
if [[ -f /etc/os-release ]]; then
|
|
||||||
os_version=$(awk -F'[= ."]' '/VERSION_ID/{print $3}' /etc/os-release)
|
|
||||||
fi
|
|
||||||
if [[ -z "$os_version" && -f /etc/lsb-release ]]; then
|
|
||||||
os_version=$(awk -F'[= ."]+' '/DISTRIB_RELEASE/{print $2}' /etc/lsb-release)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ x"${release}" == x"centos" ]]; then
|
|
||||||
if [[ ${os_version} -le 6 ]]; then
|
|
||||||
echo -e "${red} Please use CentOS 7 or higher version ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ x"${release}" == x"ubuntu" ]]; then
|
|
||||||
if [[ ${os_version} -lt 16 ]]; then
|
|
||||||
echo -e "${red} Please use Ubuntu 16 or higher version ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ x"${release}" == x"debian" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
echo -e "${red} Please use Debian 8 or higher version ${plain}\n" && exit 1
|
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
|
elif [[ "${release}" == "ubuntu" ]]; then
|
||||||
|
if [[ ${os_version} -lt 20 ]]; then
|
||||||
|
echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "${release}" == "fedora" ]]; then
|
||||||
|
if [[ ${os_version} -lt 36 ]]; then
|
||||||
|
echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "${release}" == "debian" ]]; then
|
||||||
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
|
echo -e "${red} Please use Debian 8 or higher ${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
install_base() {
|
install_base() {
|
||||||
if [[ x"${release}" == x"centos" ]]; then
|
if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]] ; then
|
||||||
yum install wget curl tar -y
|
yum install wget curl tar -y
|
||||||
else
|
else
|
||||||
apt install wget curl tar -y
|
apt install wget curl tar -y
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -217,7 +217,7 @@ func main() {
|
|||||||
|
|
||||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
||||||
var dbPath string
|
var dbPath string
|
||||||
v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path")
|
v2uiCmd.StringVar(&dbPath, "db", fmt.Sprintf("%s/v2-ui.db", config.GetDBFolderPath()), "set v2-ui db file path")
|
||||||
|
|
||||||
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
var port int
|
var port int
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 324 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 316 KiB |
@@ -1,269 +1,391 @@
|
|||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-space {
|
.ant-space {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-layout-sider-zero-width-trigger {
|
.ant-layout-sider-zero-width-trigger {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card {
|
.ant-card {
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-hoverable {
|
.ant-card-hoverable {
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card+.ant-card {
|
.ant-card+.ant-card {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-handle {
|
.drawer-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 72px;
|
top: 72px;
|
||||||
width: 41px;
|
width: 41px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
right: -40px;
|
right: -40px;
|
||||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
|
||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
.drawer-handle {
|
.drawer-handle {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-linear-enter-active, .fade-in-linear-leave-active {
|
.fade-in-linear-enter-active, .fade-in-linear-leave-active {
|
||||||
-webkit-transition: opacity .2s linear;
|
-webkit-transition: opacity .2s linear;
|
||||||
transition: opacity .2s linear
|
transition: opacity .2s linear
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-linear-enter-active, .fade-in-linear-leave-active {
|
.fade-in-linear-enter-active, .fade-in-linear-leave-active {
|
||||||
-webkit-transition: opacity .2s linear;
|
-webkit-transition: opacity .2s linear;
|
||||||
transition: opacity .2s linear
|
transition: opacity .2s linear
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-enter-active, .fade-in-leave-active {
|
.fade-in-enter-active, .fade-in-leave-active {
|
||||||
-webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1);
|
-webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1);
|
||||||
transition: all .3s cubic-bezier(.55, 0, .1, 1)
|
transition: all .3s cubic-bezier(.55, 0, .1, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-center-enter-active, .zoom-in-center-leave-active {
|
.zoom-in-center-enter-active, .zoom-in-center-leave-active {
|
||||||
-webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1);
|
-webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1);
|
||||||
transition: all .3s cubic-bezier(.55, 0, .1, 1)
|
transition: all .3s cubic-bezier(.55, 0, .1, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-center-enter, .zoom-in-center-leave-active {
|
.zoom-in-center-enter, .zoom-in-center-leave-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transform: scaleX(0);
|
-webkit-transform: scaleX(0);
|
||||||
transform: scaleX(0)
|
transform: scaleX(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-top-enter-active, .zoom-in-top-leave-active {
|
.zoom-in-top-enter-active, .zoom-in-top-leave-active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-webkit-transform: scaleY(1);
|
-webkit-transform: scaleY(1);
|
||||||
transform: scaleY(1);
|
transform: scaleY(1);
|
||||||
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
|
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
-webkit-transform-origin: center top;
|
-webkit-transform-origin: center top;
|
||||||
transform-origin: center top
|
transform-origin: center top
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-top-enter, .zoom-in-top-leave-active {
|
.zoom-in-top-enter, .zoom-in-top-leave-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transform: scaleY(0);
|
-webkit-transform: scaleY(0);
|
||||||
transform: scaleY(0)
|
transform: scaleY(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-bottom-enter-active, .zoom-in-bottom-leave-active {
|
.zoom-in-bottom-enter-active, .zoom-in-bottom-leave-active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-webkit-transform: scaleY(1);
|
-webkit-transform: scaleY(1);
|
||||||
transform: scaleY(1);
|
transform: scaleY(1);
|
||||||
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
|
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
-webkit-transform-origin: center bottom;
|
-webkit-transform-origin: center bottom;
|
||||||
transform-origin: center bottom
|
transform-origin: center bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-bottom-enter, .zoom-in-bottom-leave-active {
|
.zoom-in-bottom-enter, .zoom-in-bottom-leave-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transform: scaleY(0);
|
-webkit-transform: scaleY(0);
|
||||||
transform: scaleY(0)
|
transform: scaleY(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-left-enter-active, .zoom-in-left-leave-active {
|
.zoom-in-left-enter-active, .zoom-in-left-leave-active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-webkit-transform: scale(1, 1);
|
-webkit-transform: scale(1, 1);
|
||||||
transform: scale(1, 1);
|
transform: scale(1, 1);
|
||||||
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
|
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
|
||||||
-webkit-transform-origin: top left;
|
-webkit-transform-origin: top left;
|
||||||
transform-origin: top left
|
transform-origin: top left
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-in-left-enter, .zoom-in-left-leave-active {
|
.zoom-in-left-enter, .zoom-in-left-leave-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transform: scale(.45, .45);
|
-webkit-transform: scale(.45, .45);
|
||||||
transform: scale(.45, .45)
|
transform: scale(.45, .45)
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-enter-active, .list-leave-active {
|
.list-enter-active, .list-leave-active {
|
||||||
-webkit-transition: all .3s;
|
-webkit-transition: all .3s;
|
||||||
transition: all .3s
|
transition: all .3s
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-enter, .list-leave-active {
|
.list-enter, .list-leave-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transform: translateY(-30px);
|
-webkit-transform: translateY(-30px);
|
||||||
transform: translateY(-30px)
|
transform: translateY(-30px)
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-progress-inner {
|
.ant-progress-inner {
|
||||||
background-color: #EBEEF5;
|
background-color: #EBEEF5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deactive-client .ant-collapse-header{
|
.deactive-client .ant-collapse-header{
|
||||||
color:rgb(255, 255, 255) !important;
|
color:rgb(255, 255, 255) !important;
|
||||||
background-color: rgb(255, 127, 127);
|
background-color: rgb(255, 127, 127);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-tbody>tr>td,
|
.ant-table-tbody>tr>td,
|
||||||
.ant-table-thead>tr>th{
|
.ant-table-thead>tr>th{
|
||||||
padding:16px;
|
padding:16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark {
|
.ant-table-expand-icon-th,
|
||||||
color: hsla(0,0%,100%,.65);
|
.ant-table-row-expand-icon-cell {
|
||||||
background-color: #001529;
|
width: 30px;
|
||||||
border-color:rgba(0,0,0,.09);
|
min-width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark:hover {
|
.ant-menu-dark,
|
||||||
border-color: #e8e8e8;
|
.ant-menu-dark .ant-menu-sub,
|
||||||
}
|
.ant-layout-header,
|
||||||
|
.ant-layout-sider-dark,
|
||||||
.ant-card-dark .ant-table-thead th {
|
.ant-layout-sider-zero-width-trigger,
|
||||||
color: hsla(0,0%,100%,.65);
|
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
||||||
background-color: #000c17;
|
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
||||||
}
|
background:#161b22
|
||||||
|
}
|
||||||
.ant-card-dark .ant-table-tbody tr td,
|
|
||||||
.ant-card-dark .ant-modal-title {
|
.ant-card-dark {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
}
|
background-color: #1a212a;
|
||||||
|
border-color:rgba(0,0,0,.09);
|
||||||
.ant-card-dark .ant-collapse-content,
|
}
|
||||||
.ant-card-dark .ant-calendar,
|
|
||||||
.ant-card-dark .ant-table-placeholder {
|
.ant-card-dark:hover {
|
||||||
color: hsla(0,0%,100%,.65);
|
border-color: #e8e8e8;
|
||||||
background-color: #001529;
|
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-list-item-meta-title,
|
.ant-card-dark .ant-table-thead th {
|
||||||
.ant-card-dark .ant-list-item-meta-description,
|
color: hsla(0,0%,100%,.65);
|
||||||
.ant-card-dark .ant-form-item-label>label,
|
background-color: #161b22;
|
||||||
.ant-card-dark .ant-form-item,
|
}
|
||||||
.ant-card-dark .ant-divider-inner-text,
|
|
||||||
.ant-card-dark .ant-modal-confirm-content,
|
.ant-card-dark .ant-table-tbody tr td,
|
||||||
.ant-card-dark .ant-modal-confirm-title,
|
.ant-card-dark .ant-modal-title {
|
||||||
.ant-card-dark .ant-progress-text,
|
color: hsla(0,0%,100%,.65);
|
||||||
.ant-card-dark .ant-modal-close,
|
}
|
||||||
.ant-card-dark i,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item,
|
.ant-card-dark .ant-collapse-content,
|
||||||
.ant-card-dark .ant-calendar-month-select,
|
.ant-card-dark .ant-calendar,
|
||||||
.ant-card-dark .ant-calendar-year-select,
|
.ant-card-dark .ant-table-placeholder,
|
||||||
.ant-card-dark .ant-calendar-date,
|
.ant-card-dark .ant-input-group-addon {
|
||||||
.ant-card-dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,
|
color: hsla(0,0%,100%,.65);
|
||||||
.ant-card-dark .ant-empty-normal {
|
background-color: #262f3d;
|
||||||
color: hsla(0,0%,100%,.65);
|
}
|
||||||
}
|
|
||||||
|
.ant-card-dark .ant-list-item-meta-title,
|
||||||
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
|
.ant-card-dark .ant-list-item-meta-description,
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
.ant-card-dark .ant-form-item-label>label,
|
||||||
.ant-card-dark .ant-calendar-date:hover {
|
.ant-card-dark .ant-form-item,
|
||||||
background-color: #004488;
|
.ant-card-dark .ant-divider-inner-text,
|
||||||
}
|
.ant-card-dark .ant-modal-confirm-content,
|
||||||
|
.ant-card-dark .ant-modal-confirm-title,
|
||||||
.ant-card-dark tbody .ant-table-expanded-row {
|
.ant-card-dark .ant-progress-text,
|
||||||
color: hsla(0,0%,100%,.65);
|
.ant-card-dark .ant-modal-close,
|
||||||
background-color: #023366;
|
.ant-card-dark i,
|
||||||
}
|
.ant-card-dark .ant-select-dropdown-menu-item,
|
||||||
|
.ant-card-dark .ant-calendar-month-select,
|
||||||
.ant-card-dark .ant-input,
|
.ant-card-dark .ant-calendar-year-select,
|
||||||
.ant-card-dark .ant-input-number,
|
.ant-card-dark .ant-calendar-date,
|
||||||
.ant-card-dark .ant-calendar-input,
|
.ant-card-dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item-selected,
|
.ant-card-dark .ant-empty-normal,
|
||||||
.ant-card-dark .ant-select-selection {
|
.ant-card-dark .ant-checkbox+span {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #023366;
|
}
|
||||||
}
|
|
||||||
|
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
|
||||||
.ant-card-dark .ant-collapse-item {
|
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
||||||
color: hsla(0,0%,100%,.65);
|
.ant-card-dark .ant-calendar-date:hover,
|
||||||
background-color: #000c17;
|
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
||||||
}
|
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
||||||
|
background-color: #004488;
|
||||||
.ant-card-dark .ant-modal-content,
|
}
|
||||||
.ant-card-dark .ant-modal-body,
|
|
||||||
.ant-card-dark .ant-modal-header,
|
.ant-card-dark tbody .ant-table-expanded-row,
|
||||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
.ant-card-dark .ant-calendar-time-picker-inner {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #1C262D;
|
background-color: #1a212a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-table-header {
|
.ant-card-dark .ant-input,
|
||||||
background-color: #f0f2f5;
|
.ant-card-dark .ant-input-number,
|
||||||
}
|
.ant-card-dark .ant-input-number-handler-wrap,
|
||||||
|
.ant-card-dark .ant-calendar-input,
|
||||||
.client-table-odd-row {
|
.ant-card-dark .ant-select-dropdown-menu-item-selected,
|
||||||
background-color: #fafafa;
|
.ant-card-dark .ant-select-selection,
|
||||||
}
|
.ant-card-dark .ant-calendar-picker-clear {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
.ant-card-dark .client-table-header {
|
background-color: #2e3b52;
|
||||||
background-color: #023366;
|
}
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
.ant-card-dark .client-table-odd-row {
|
background-color: #242c3a;
|
||||||
color: hsla(0,0%,100%,.65);
|
}
|
||||||
background-color: #1C262D;
|
|
||||||
}
|
.ant-card-dark .ant-collapse-item {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
.ant-card-dark .ant-calendar-last-month-cell .ant-calendar-date,
|
background-color: #161b22;
|
||||||
.ant-card-dark .ant-calendar-next-month-btn-day .ant-calendar-date {
|
}
|
||||||
color: hsla(0,0%,100%,.30);
|
|
||||||
}
|
.ant-dropdown-menu-dark,
|
||||||
|
.ant-card-dark .ant-modal-content {
|
||||||
.ant-drawer-dark {
|
border: 1px solid rgba(255, 255, 255, 0.65);
|
||||||
color: hsla(0,0%,100%,.65);
|
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-drawer-dark .ant-drawer-wrapper-body,
|
.ant-card-dark .ant-modal-content,
|
||||||
.ant-drawer-dark .drawer-handle {
|
.ant-card-dark .ant-modal-body,
|
||||||
background-color: #001529;
|
.ant-card-dark .ant-modal-header,
|
||||||
border: 1px solid hsla(0,0%,100%,.30);
|
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #222a37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-table-header {
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-table-odd-row {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .client-table-header {
|
||||||
|
background-color: #1a212a;
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .client-table-odd-row {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #242c3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-calendar-last-month-cell .ant-calendar-date,
|
||||||
|
.ant-card-dark .ant-calendar-next-month-btn-day .ant-calendar-date {
|
||||||
|
color: hsla(0,0%,100%,.30);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer-dark {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer-dark .ant-drawer-wrapper-body,
|
||||||
|
.ant-drawer-dark .drawer-handle {
|
||||||
|
background-color: #1a212a;
|
||||||
|
border: 1px solid hsla(0,0%,100%,.30);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-tag {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background: rgba(255,255,255,.04);
|
||||||
|
border-color: #434343;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-tag-blue {
|
||||||
|
color: #3c9ae8;
|
||||||
|
background: #111d2c;
|
||||||
|
border-color: #15395b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-tag-green {
|
||||||
|
color: #6abe39;
|
||||||
|
background: #162312;
|
||||||
|
border-color: #274916;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-tag-cyan {
|
||||||
|
color: #33bcb7;
|
||||||
|
background: #112123;
|
||||||
|
border-color: #144848;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-tag-red {
|
||||||
|
color: #e84749;
|
||||||
|
background: #2a1215;
|
||||||
|
border-color: #58181c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-tag-orange {
|
||||||
|
color: #e89a3c;
|
||||||
|
background: #2b1d11;
|
||||||
|
border-color: #593815;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-table-row-expand-icon,
|
||||||
|
.ant-card-dark .ant-checkbox-inner {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-switch-checked {
|
||||||
|
background-color: #0c61b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-btn,
|
||||||
|
.ant-card-dark .ant-radio-button-wrapper {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background: none;
|
||||||
|
border: 1px solid hsla(0,0%,100%,.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-radio-button-wrapper:hover {
|
||||||
|
color: #177ddc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-btn-primary {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #073763;
|
||||||
|
border-color: #1890ff;
|
||||||
|
text-shadow: 0 -1px 0 rgba(255,255,255,.12);
|
||||||
|
box-shadow: 0 2px 0 rgba(255,255,255,.045);
|
||||||
|
}
|
||||||
|
.ant-card-dark .ant-btn-primary:hover {
|
||||||
|
background-color: #40a9ff;
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dark .ant-popover-content {
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dark .ant-popover-inner {
|
||||||
|
background: #222a37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dark .ant-popover-title,
|
||||||
|
.ant-dark .ant-popover-inner-content {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dark .ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow {
|
||||||
|
border-color: transparent #2e3b52 #2e3b52 transparent;
|
||||||
}
|
}
|
||||||
BIN
web/assets/favicon.ico
Normal file
BIN
web/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@@ -170,13 +170,13 @@ class AllSetting {
|
|||||||
this.webCertFile = "";
|
this.webCertFile = "";
|
||||||
this.webKeyFile = "";
|
this.webKeyFile = "";
|
||||||
this.webBasePath = "/";
|
this.webBasePath = "/";
|
||||||
|
this.expireDiff = "";
|
||||||
|
this.trafficDiff = "";
|
||||||
this.tgBotEnable = false;
|
this.tgBotEnable = false;
|
||||||
this.tgBotToken = "";
|
this.tgBotToken = "";
|
||||||
this.tgBotChatId = "";
|
this.tgBotChatId = "";
|
||||||
this.tgRunTime = "@daily";
|
this.tgRunTime = "@daily";
|
||||||
this.tgBotBackup = false;
|
this.tgBotBackup = false;
|
||||||
this.tgExpireDiff = "";
|
|
||||||
this.tgTrafficDiff = "";
|
|
||||||
this.tgCpu = "";
|
this.tgCpu = "";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
|
|
||||||
|
|||||||
@@ -43,13 +43,9 @@ const RULE_DOMAIN = {
|
|||||||
SPEEDTEST: 'geosite:speedtest',
|
SPEEDTEST: 'geosite:speedtest',
|
||||||
};
|
};
|
||||||
|
|
||||||
const XTLS_FLOW_CONTROL = {
|
|
||||||
ORIGIN: "xtls-rprx-origin",
|
|
||||||
DIRECT: "xtls-rprx-direct",
|
|
||||||
};
|
|
||||||
|
|
||||||
const TLS_FLOW_CONTROL = {
|
const TLS_FLOW_CONTROL = {
|
||||||
VISION: "xtls-rprx-vision",
|
VISION: "xtls-rprx-vision",
|
||||||
|
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||||
};
|
};
|
||||||
|
|
||||||
const TLS_VERSION_OPTION = {
|
const TLS_VERSION_OPTION = {
|
||||||
@@ -93,9 +89,9 @@ const UTLS_FINGERPRINT = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ALPN_OPTION = {
|
const ALPN_OPTION = {
|
||||||
|
H3: "h3",
|
||||||
H2: "h2",
|
H2: "h2",
|
||||||
HTTP1: "http/1.1",
|
HTTP1: "http/1.1",
|
||||||
BOTH: "h2,http/1.1",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
@@ -103,7 +99,6 @@ Object.freeze(VmessMethods);
|
|||||||
Object.freeze(SSMethods);
|
Object.freeze(SSMethods);
|
||||||
Object.freeze(RULE_IP);
|
Object.freeze(RULE_IP);
|
||||||
Object.freeze(RULE_DOMAIN);
|
Object.freeze(RULE_DOMAIN);
|
||||||
Object.freeze(XTLS_FLOW_CONTROL);
|
|
||||||
Object.freeze(TLS_FLOW_CONTROL);
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
Object.freeze(TLS_VERSION_OPTION);
|
Object.freeze(TLS_VERSION_OPTION);
|
||||||
Object.freeze(TLS_CIPHER_OPTION);
|
Object.freeze(TLS_CIPHER_OPTION);
|
||||||
@@ -474,12 +469,12 @@ class GrpcStreamSettings extends XrayCommonClass {
|
|||||||
|
|
||||||
class TlsStreamSettings extends XrayCommonClass {
|
class TlsStreamSettings extends XrayCommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
minVersion = TLS_VERSION_OPTION.TLS12,
|
minVersion = TLS_VERSION_OPTION.TLS10,
|
||||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
maxVersion = TLS_VERSION_OPTION.TLS12,
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
certificates=[new TlsStreamSettings.Cert()],
|
certificates=[new TlsStreamSettings.Cert()],
|
||||||
alpn=[''],
|
alpn=[],
|
||||||
settings=[new TlsStreamSettings.Settings()]) {
|
settings=new TlsStreamSettings.Settings()) {
|
||||||
super();
|
super();
|
||||||
this.server = serverName;
|
this.server = serverName;
|
||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
@@ -506,8 +501,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
let values = json.settings[0];
|
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
||||||
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
|
|
||||||
}
|
}
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
@@ -528,7 +522,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
settings: TlsStreamSettings.toJsonArray(this.settings),
|
settings: this.settings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,9 +569,9 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(insecure = false, fingerprint = '', serverName = '') {
|
constructor(allowInsecure = false, fingerprint = '', serverName = '') {
|
||||||
super();
|
super();
|
||||||
this.inSecure = insecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
}
|
}
|
||||||
@@ -590,17 +584,99 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
allowInsecure: this.inSecure,
|
allowInsecure: this.allowInsecure,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
serverName: this.serverName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class RealityStreamSettings extends XrayCommonClass {
|
||||||
|
constructor(show = false, xver = 0,
|
||||||
|
dest = 'microsoft.com:443',
|
||||||
|
serverNames = 'microsoft.com,www.microsoft.com',
|
||||||
|
privateKey = '', minClient = '', maxClient = '',
|
||||||
|
maxTimediff = 0, shortIds = [],
|
||||||
|
settings= new RealityStreamSettings.Settings()) {
|
||||||
|
super();
|
||||||
|
this.show = show;
|
||||||
|
this.xver = xver;
|
||||||
|
this.dest = dest;
|
||||||
|
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
this.minClient = minClient;
|
||||||
|
this.maxClient = maxClient;
|
||||||
|
this.maxTimediff = maxTimediff;
|
||||||
|
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
let settings;
|
||||||
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
|
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName, json.settings.spiderX);
|
||||||
|
}
|
||||||
|
return new RealityStreamSettings(
|
||||||
|
json.show,
|
||||||
|
json.xver,
|
||||||
|
json.dest,
|
||||||
|
json.serverNames,
|
||||||
|
json.privateKey,
|
||||||
|
json.minClient,
|
||||||
|
json.maxClient,
|
||||||
|
json.maxTimediff,
|
||||||
|
json.shortIds,
|
||||||
|
json.settings,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
show: this.show,
|
||||||
|
xver: this.xver,
|
||||||
|
dest: this.dest,
|
||||||
|
serverNames: this.serverNames.split(","),
|
||||||
|
privateKey: this.privateKey,
|
||||||
|
minClient: this.minClient,
|
||||||
|
maxClient: this.maxClient,
|
||||||
|
maxTimediff: this.maxTimediff,
|
||||||
|
shortIds: this.shortIds.split(","),
|
||||||
|
settings: this.settings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
|
constructor(publicKey = '', fingerprint = '', serverName = '', spiderX= '/') {
|
||||||
|
super();
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.spiderX = spiderX;
|
||||||
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new RealityStreamSettings.Settings(
|
||||||
|
json.publicKey,
|
||||||
|
json.fingerprint,
|
||||||
|
json.serverName,
|
||||||
|
json.spiderX,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
publicKey: this.publicKey,
|
||||||
|
fingerprint: this.fingerprint,
|
||||||
|
serverName: this.serverName,
|
||||||
|
spiderX: this.spiderX,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class StreamSettings extends XrayCommonClass {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
tlsSettings=new TlsStreamSettings(),
|
tlsSettings=new TlsStreamSettings(),
|
||||||
|
realitySettings = new RealityStreamSettings(),
|
||||||
tcpSettings=new TcpStreamSettings(),
|
tcpSettings=new TcpStreamSettings(),
|
||||||
kcpSettings=new KcpStreamSettings(),
|
kcpSettings=new KcpStreamSettings(),
|
||||||
wsSettings=new WsStreamSettings(),
|
wsSettings=new WsStreamSettings(),
|
||||||
@@ -612,6 +688,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
this.network = network;
|
this.network = network;
|
||||||
this.security = security;
|
this.security = security;
|
||||||
this.tls = tlsSettings;
|
this.tls = tlsSettings;
|
||||||
|
this.reality = realitySettings;
|
||||||
this.tcp = tcpSettings;
|
this.tcp = tcpSettings;
|
||||||
this.kcp = kcpSettings;
|
this.kcp = kcpSettings;
|
||||||
this.ws = wsSettings;
|
this.ws = wsSettings;
|
||||||
@@ -632,29 +709,25 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isXTls() {
|
get isReality() {
|
||||||
return this.security === "xtls";
|
return this.security === "reality";
|
||||||
}
|
}
|
||||||
|
|
||||||
set isXTls(isXTls) {
|
set isReality(isReality) {
|
||||||
if (isXTls) {
|
if (isReality) {
|
||||||
this.security = 'xtls';
|
this.security = 'reality';
|
||||||
} else {
|
} else {
|
||||||
this.security = 'none';
|
this.security = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
let tls;
|
|
||||||
if (json.security === "xtls") {
|
|
||||||
tls = TlsStreamSettings.fromJson(json.xtlsSettings);
|
|
||||||
} else {
|
|
||||||
tls = TlsStreamSettings.fromJson(json.tlsSettings);
|
|
||||||
}
|
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
json.network,
|
json.network,
|
||||||
json.security,
|
json.security,
|
||||||
tls,
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||||
WsStreamSettings.fromJson(json.wsSettings),
|
WsStreamSettings.fromJson(json.wsSettings),
|
||||||
@@ -670,7 +743,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
network: network,
|
network: network,
|
||||||
security: this.security,
|
security: this.security,
|
||||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||||
xtlsSettings: this.isXTls ? this.tls.toJson() : undefined,
|
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||||
@@ -750,13 +823,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get xtls() {
|
get reality() {
|
||||||
return this.stream.security === 'xtls';
|
return this.stream.security === 'reality';
|
||||||
}
|
}
|
||||||
|
|
||||||
set xtls(isXTls) {
|
set reality(isReality) {
|
||||||
if (isXTls) {
|
if (isReality) {
|
||||||
this.stream.security = 'xtls';
|
this.stream.security = 'reality';
|
||||||
} else {
|
} else {
|
||||||
this.stream.security = 'none';
|
this.stream.security = 'none';
|
||||||
}
|
}
|
||||||
@@ -865,7 +938,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
if (this.stream.isTls || this.stream.isXTls) {
|
if (this.stream.isTls || this.stream.isReality) {
|
||||||
return this.stream.tls.server;
|
return this.stream.tls.server;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
@@ -920,16 +993,16 @@ class Inbound extends XrayCommonClass {
|
|||||||
isExpiry(index) {
|
isExpiry(index) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
if(this.settings.vmesses[index]._expiryTime != null)
|
if(this.settings.vmesses[index].expiryTime > 0)
|
||||||
return this.settings.vmesses[index]._expiryTime < new Date().getTime();
|
return this.settings.vmesses[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
if(this.settings.vlesses[index]._expiryTime != null)
|
if(this.settings.vlesses[index].expiryTime > 0)
|
||||||
return this.settings.vlesses[index]._expiryTime < new Date().getTime();
|
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if(this.settings.trojans[index]._expiryTime != null)
|
if(this.settings.trojans[index].expiryTime > 0)
|
||||||
return this.settings.trojans[index]._expiryTime < new Date().getTime();
|
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -961,7 +1034,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
//this is used for xtls-rprx-vision
|
//this is used for xtls-rprx-vision
|
||||||
canEnableTlsFlow() {
|
canEnableTlsFlow() {
|
||||||
if ((this.stream.security === 'tls') && (this.network === "tcp")) {
|
if ((this.stream.security != 'none') && (this.network === "tcp")) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
return true;
|
return true;
|
||||||
@@ -976,7 +1049,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.canEnableTls();
|
return this.canEnableTls();
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableXTls() {
|
canEnableReality() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
@@ -984,7 +1057,15 @@ class Inbound extends XrayCommonClass {
|
|||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.network === "tcp";
|
|
||||||
|
switch (this.network) {
|
||||||
|
case "tcp":
|
||||||
|
case "http":
|
||||||
|
case "grpc":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableStream() {
|
canEnableStream() {
|
||||||
@@ -1081,6 +1162,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
host: host,
|
host: host,
|
||||||
path: path,
|
path: path,
|
||||||
tls: this.stream.security,
|
tls: this.stream.security,
|
||||||
|
sni: this.stream.tls.settings.serverName,
|
||||||
|
fp: this.stream.tls.settings.fingerprint,
|
||||||
|
alpn: this.stream.tls.alpn.join(','),
|
||||||
|
allowInsecure: this.stream.tls.settings.allowInsecure,
|
||||||
};
|
};
|
||||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||||
}
|
}
|
||||||
@@ -1092,7 +1177,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
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);
|
||||||
params.set("security", this.stream.security);
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
const tcp = this.stream.tcp;
|
const tcp = this.stream.tcp;
|
||||||
@@ -1139,25 +1223,43 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.tls) {
|
if (this.tls) {
|
||||||
params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
|
params.set("security", "tls");
|
||||||
params.set("alpn", this.stream.tls.alpn[0]);
|
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.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.tls.server;
|
||||||
}
|
}
|
||||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
if (this.stream.tls.settings.serverName !== ''){
|
||||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
params.set("sni", this.stream.tls.settings.serverName);
|
||||||
}
|
}
|
||||||
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.xtls) {
|
if (this.reality) {
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
params.set("security", "reality");
|
||||||
address = this.stream.tls.server;
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
if (type === "tcp") {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
|
if (this.stream.reality.shortIds != "") {
|
||||||
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
|
}
|
||||||
|
if (this.stream.reality.fingerprint != "") {
|
||||||
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
|
address = this.stream.reality.settings.serverName;
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
|
}
|
||||||
|
if (this.stream.network === 'tcp') {
|
||||||
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1190,7 +1292,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
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);
|
||||||
params.set("security", this.stream.security);
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
const tcp = this.stream.tcp;
|
const tcp = this.stream.tcp;
|
||||||
@@ -1237,23 +1338,41 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.tls) {
|
if (this.tls) {
|
||||||
params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
|
params.set("security", "tls");
|
||||||
params.set("alpn", this.stream.tls.alpn[0]);
|
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.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.tls.server;
|
||||||
}
|
}
|
||||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
if (this.stream.tls.settings.serverName !== ''){
|
||||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
params.set("sni", this.stream.tls.settings.serverName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.xtls) {
|
if (this.reality) {
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
params.set("security", "reality");
|
||||||
address = this.stream.tls.server;
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
if (type === "tcp" && this.settings.trojans[clientIndex].flow.length > 0) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
}
|
if (this.stream.reality.shortIds != "") {
|
||||||
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
|
}
|
||||||
|
if (this.stream.reality.fingerprint != "") {
|
||||||
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
|
address = this.stream.reality.settings.serverName;
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
|
}
|
||||||
|
if (this.stream.network === 'tcp') {
|
||||||
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
||||||
@@ -1416,13 +1535,16 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(), totalGB=0, expiryTime='') {
|
constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.alterId = alterId;
|
this.alterId = alterId;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
|
this.enable = enable;
|
||||||
|
this.tgId = tgId;
|
||||||
|
this.subId = subId;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -1432,13 +1554,18 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
json.email,
|
json.email,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
json.expiryTime,
|
json.expiryTime,
|
||||||
|
json.enable,
|
||||||
|
json.tgId,
|
||||||
|
json.subId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
get _expiryTime() {
|
get _expiryTime() {
|
||||||
if (this.expiryTime === 0 || this.expiryTime === "") {
|
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (this.expiryTime < 0){
|
||||||
|
return this.expiryTime / -86400000;
|
||||||
|
}
|
||||||
return moment(this.expiryTime);
|
return moment(this.expiryTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1491,22 +1618,23 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
|
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
|
||||||
decryption: this.decryption,
|
decryption: 'none',
|
||||||
fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
|
fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
|
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
||||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime='') {
|
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
|
this.enable = enable;
|
||||||
|
this.tgId = tgId;
|
||||||
|
this.subId = subId;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -1516,6 +1644,9 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
json.email,
|
json.email,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
json.expiryTime,
|
json.expiryTime,
|
||||||
|
json.enable,
|
||||||
|
json.tgId,
|
||||||
|
json.subId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1523,6 +1654,9 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
if (this.expiryTime === 0 || this.expiryTime === "") {
|
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (this.expiryTime < 0){
|
||||||
|
return this.expiryTime / -86400000;
|
||||||
|
}
|
||||||
return moment(this.expiryTime);
|
return moment(this.expiryTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1612,13 +1746,16 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime='') {
|
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
|
this.enable = enable;
|
||||||
|
this.tgId = tgId;
|
||||||
|
this.subId = subId;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -1628,6 +1765,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
email: this.email,
|
email: this.email,
|
||||||
totalGB: this.totalGB,
|
totalGB: this.totalGB,
|
||||||
expiryTime: this.expiryTime,
|
expiryTime: this.expiryTime,
|
||||||
|
enable: this.enable,
|
||||||
|
tgId: this.tgId,
|
||||||
|
subId: this.subId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1638,7 +1778,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
json.email,
|
json.email,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
json.expiryTime,
|
json.expiryTime,
|
||||||
|
json.enable,
|
||||||
|
json.tgId,
|
||||||
|
json.subId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1646,6 +1788,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
if (this.expiryTime === 0 || this.expiryTime === "") {
|
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (this.expiryTime < 0){
|
||||||
|
return this.expiryTime / -86400000;
|
||||||
|
}
|
||||||
return moment(this.expiryTime);
|
return moment(this.expiryTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import "github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type APIController struct {
|
type APIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
settingController *SettingController
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIController(g *gin.RouterGroup) *APIController {
|
func NewAPIController(g *gin.RouterGroup) *APIController {
|
||||||
@@ -26,6 +22,12 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/add", a.addInbound)
|
g.POST("/add", a.addInbound)
|
||||||
g.POST("/del/:id", a.delInbound)
|
g.POST("/del/:id", a.delInbound)
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
|
g.POST("/addClient/", a.addInboundClient)
|
||||||
|
g.POST("/delClient/:email", a.delInboundClient)
|
||||||
|
g.POST("/updateClient/:index", a.updateInboundClient)
|
||||||
|
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
}
|
}
|
||||||
@@ -45,3 +47,21 @@ func (a *APIController) delInbound(c *gin.Context) {
|
|||||||
func (a *APIController) updateInbound(c *gin.Context) {
|
func (a *APIController) updateInbound(c *gin.Context) {
|
||||||
a.inboundController.updateInbound(c)
|
a.inboundController.updateInbound(c)
|
||||||
}
|
}
|
||||||
|
func (a *APIController) addInboundClient(c *gin.Context) {
|
||||||
|
a.inboundController.addInboundClient(c)
|
||||||
|
}
|
||||||
|
func (a *APIController) delInboundClient(c *gin.Context) {
|
||||||
|
a.inboundController.delInboundClient(c)
|
||||||
|
}
|
||||||
|
func (a *APIController) updateInboundClient(c *gin.Context) {
|
||||||
|
a.inboundController.updateInboundClient(c)
|
||||||
|
}
|
||||||
|
func (a *APIController) resetClientTraffic(c *gin.Context) {
|
||||||
|
a.inboundController.resetClientTraffic(c)
|
||||||
|
}
|
||||||
|
func (a *APIController) resetAllTraffics(c *gin.Context) {
|
||||||
|
a.inboundController.resetAllTraffics(c)
|
||||||
|
}
|
||||||
|
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
||||||
|
a.inboundController.resetAllClientTraffics(c)
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/add", a.addInbound)
|
g.POST("/add", a.addInbound)
|
||||||
g.POST("/del/:id", a.delInbound)
|
g.POST("/del/:id", a.delInbound)
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
g.POST("/addClient/", a.addInboundClient)
|
g.POST("/addClient", a.addInboundClient)
|
||||||
g.POST("/delClient/:email", a.delInboundClient)
|
g.POST("/delClient/:email", a.delInboundClient)
|
||||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
g.POST("/updateClient/:index", a.updateInboundClient)
|
||||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,19 +129,19 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||||
inbound := &model.Inbound{}
|
data := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.AddInboundClient(inbound)
|
err = a.inboundService.AddInboundClient(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "something worng!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -208,3 +210,27 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||||
|
err := a.inboundService.ResetAllTraffics()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "something worng!", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(c, "All traffics reseted", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||||
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.inboundService.ResetAllClientTraffics(id)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "something worng!", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(c, "All traffics of client reseted", nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"time"
|
"time"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerController struct {
|
type ServerController struct {
|
||||||
@@ -37,6 +38,10 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/stopXrayService", a.stopXrayService)
|
g.POST("/stopXrayService", a.stopXrayService)
|
||||||
g.POST("/restartXrayService", a.restartXrayService)
|
g.POST("/restartXrayService", a.restartXrayService)
|
||||||
g.POST("/installXray/:version", a.installXray)
|
g.POST("/installXray/:version", a.installXray)
|
||||||
|
g.POST("/logs/:count", a.getLogs)
|
||||||
|
g.POST("/getConfigJson", a.getConfigJson)
|
||||||
|
g.GET("/getDb", a.getDb)
|
||||||
|
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
@@ -87,13 +92,13 @@ func (a *ServerController) installXray(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) stopXrayService(c *gin.Context) {
|
func (a *ServerController) stopXrayService(c *gin.Context) {
|
||||||
a.lastGetStatusTime = time.Now()
|
a.lastGetStatusTime = time.Now()
|
||||||
err := a.serverService.StopXrayService()
|
err := a.serverService.StopXrayService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "", err)
|
jsonMsg(c, "", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Xray stoped",err)
|
jsonMsg(c, "Xray stoped", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
func (a *ServerController) restartXrayService(c *gin.Context) {
|
func (a *ServerController) restartXrayService(c *gin.Context) {
|
||||||
@@ -102,6 +107,48 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
|
|||||||
jsonMsg(c, "", err)
|
jsonMsg(c, "", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Xray restarted",err)
|
jsonMsg(c, "Xray restarted", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getLogs(c *gin.Context) {
|
||||||
|
count := c.Param("count")
|
||||||
|
logs, err := a.serverService.GetLogs(count)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "getLogs", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, logs, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||||
|
configJson, err := a.serverService.GetConfigJson()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "get config.json", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, configJson, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getDb(c *gin.Context) {
|
||||||
|
db, err := a.serverService.GetDb()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "get Database", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Set the headers for the response
|
||||||
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
|
c.Header("Content-Disposition", "attachment; filename=x-ui.db")
|
||||||
|
|
||||||
|
// Write the file contents to the response
|
||||||
|
c.Writer.Write(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
|
||||||
|
cert, err := a.serverService.GetNewX25519Cert()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "get x25519 certificate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, cert, nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
g = g.Group("/setting")
|
g = g.Group("/setting")
|
||||||
|
|
||||||
g.POST("/all", a.getAllSetting)
|
g.POST("/all", a.getAllSetting)
|
||||||
|
g.POST("/defaultSettings", a.getDefaultSettings)
|
||||||
g.POST("/update", a.updateSetting)
|
g.POST("/update", a.updateSetting)
|
||||||
g.POST("/updateUser", a.updateUser)
|
g.POST("/updateUser", a.updateUser)
|
||||||
g.POST("/restartPanel", a.restartPanel)
|
g.POST("/restartPanel", a.restartPanel)
|
||||||
@@ -47,6 +48,36 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
|||||||
jsonObj(c, allSetting, nil)
|
jsonObj(c, allSetting, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
|
expireDiff, err := a.settingService.GetExpireDiff()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trafficDiff, err := a.settingService.GetTrafficDiff()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defaultCert, err := a.settingService.GetCertFile()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defaultKey, err := a.settingService.GetKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"expireDiff": expireDiff,
|
||||||
|
"trafficDiff": trafficDiff,
|
||||||
|
"defaultCert": defaultCert,
|
||||||
|
"defaultKey": defaultKey,
|
||||||
|
}
|
||||||
|
jsonObj(c, result, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *SettingController) updateSetting(c *gin.Context) {
|
func (a *SettingController) updateSetting(c *gin.Context) {
|
||||||
allSetting := &entity.AllSetting{}
|
allSetting := &entity.AllSetting{}
|
||||||
err := c.ShouldBind(allSetting)
|
err := c.ShouldBind(allSetting)
|
||||||
|
|||||||
42
web/controller/sub.go
Normal file
42
web/controller/sub.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SUBController struct {
|
||||||
|
BaseController
|
||||||
|
|
||||||
|
subService service.SubService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
||||||
|
a := &SUBController{}
|
||||||
|
a.initRouter(g)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||||
|
g = g.Group("/sub")
|
||||||
|
|
||||||
|
g.GET("/:subid", a.subs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
|
subId := c.Param("subid")
|
||||||
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
|
subs, err := a.subService.GetSubs(subId, host)
|
||||||
|
if err != nil {
|
||||||
|
c.String(400, "Error!")
|
||||||
|
} else {
|
||||||
|
result := ""
|
||||||
|
for _, sub := range subs {
|
||||||
|
result += sub + "\n"
|
||||||
|
}
|
||||||
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,13 +32,13 @@ type AllSetting struct {
|
|||||||
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"`
|
||||||
|
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||||
|
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||||
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
||||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
TgExpireDiff int `json:"tgExpireDiff" form:"tgExpireDiff"`
|
|
||||||
TgTrafficDiff int `json:"tgTrafficDiff" form:"tgTrafficDiff"`
|
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<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.2/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="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||||
<style>
|
<style>
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{{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" width="300px" :ok-text="qrModal.okText"
|
:closable="true"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}">
|
:footer="null"
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
width="300px">
|
||||||
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
||||||
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
@@ -14,17 +15,15 @@
|
|||||||
content: '',
|
content: '',
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
okText: '',
|
|
||||||
copyText: '',
|
copyText: '',
|
||||||
qrcode: null,
|
qrcode: null,
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
show: function (title='', content='', dbInbound=new DBInbound(),okText='{{ i18n "copy" }}', copyText='') {
|
show: function (title='', content='', dbInbound=new DBInbound(), copyText='') {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.okText = okText;
|
|
||||||
if (ObjectUtil.isEmpty(copyText)) {
|
if (ObjectUtil.isEmpty(copyText)) {
|
||||||
this.copyText = content;
|
this.copyText = content;
|
||||||
} else {
|
} else {
|
||||||
@@ -32,13 +31,6 @@
|
|||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
qrModalApp.$nextTick(() => {
|
qrModalApp.$nextTick(() => {
|
||||||
this.clipboard = new ClipboardJS('#qr-modal-ok-btn', {
|
|
||||||
text: () => this.copyText,
|
|
||||||
});
|
|
||||||
this.clipboard.on('success', () => {
|
|
||||||
app.$message.success('{{ i18n "copied" }}')
|
|
||||||
this.clipboard.destroy();
|
|
||||||
});
|
|
||||||
if (this.qrcode === null) {
|
if (this.qrcode === null) {
|
||||||
this.qrcode = new QRious({
|
this.qrcode = new QRious({
|
||||||
element: document.querySelector('#qrCode'),
|
element: document.querySelector('#qrCode'),
|
||||||
|
|||||||
@@ -4,63 +4,140 @@
|
|||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<tr>
|
||||||
<a-select-option :value="0">Random</a-select-option>
|
<td>{{ i18n "pages.client.method" }}</td>
|
||||||
<a-select-option :value="1">Random_Prefix</a-select-option>
|
<td>
|
||||||
<a-select-option :value="2">Random_Prefix+Num</a-select-option>
|
<a-form-item>
|
||||||
<a-select-option :value="3">Random_Prefix+Num+Postfix</a-select-option>
|
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
|
||||||
<a-select-option :value="4">Random_Prefix+Num@Telegram Username</a-select-option>
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
</a-select>
|
<a-select-option :value="0">Random</a-select-option>
|
||||||
</a-form-item><br />
|
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod>1">
|
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||||
<span slot="label">{{ i18n "pages.client.first" }}</span>
|
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
||||||
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod>1">
|
</a-form-item>
|
||||||
<span slot="label">{{ i18n "pages.client.last" }}</span>
|
</td>
|
||||||
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
</tr>
|
||||||
</a-form-item>
|
<tr v-if="clientsBulkModal.emailMethod>1">
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod>0">
|
<td>{{ i18n "pages.client.first" }}</td>
|
||||||
<span slot="label">{{ i18n "pages.client.prefix" }}</span>
|
<td>
|
||||||
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 120px"></a-input>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod>2">
|
</a-form-item>
|
||||||
<span slot="label" v-if="clientsBulkModal.emailMethod == 4">tg_uname</span>
|
</td>
|
||||||
<span slot="label" v-else>{{ i18n "pages.client.postfix" }}</span>
|
</tr>
|
||||||
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 120px"></a-input>
|
<tr v-if="clientsBulkModal.emailMethod>1">
|
||||||
</a-form-item>
|
<td>{{ i18n "pages.client.last" }}</td>
|
||||||
|
<td>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod < 2">
|
<a-form-item>
|
||||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
<a-form-item>
|
</tr>
|
||||||
<span slot="label">
|
<tr v-if="clientsBulkModal.emailMethod>0">
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<td>{{ i18n "pages.client.prefix" }}</td>
|
||||||
<a-tooltip>
|
<td>
|
||||||
<template slot="title">
|
<a-form-item>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
|
||||||
</template>
|
</a-form-item>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</td>
|
||||||
</a-tooltip>
|
</tr>
|
||||||
</span>
|
<tr v-if="clientsBulkModal.emailMethod>2">
|
||||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
<td>{{ i18n "pages.client.postfix" }}</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
</a-form-item>
|
||||||
<a-tooltip>
|
</td>
|
||||||
<template slot="title">
|
</tr>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<tr v-if="clientsBulkModal.emailMethod < 2">
|
||||||
</template>
|
<td>{{ i18n "pages.client.clientCount" }}</td>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<td>
|
||||||
</a-tooltip>
|
<a-form-item>
|
||||||
</span>
|
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
</a-form-item>
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
</td>
|
||||||
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
|
</tr>
|
||||||
</a-form-item>
|
<tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
||||||
|
<td>Flow</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Subscription</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Telegram Username</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="clientsBulkModal.delayedStart">
|
||||||
|
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else>
|
||||||
|
<td>
|
||||||
|
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
@@ -73,7 +150,6 @@
|
|||||||
confirm: null,
|
confirm: null,
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
clients: [],
|
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
totalGB: 0,
|
totalGB: 0,
|
||||||
expiryTime: '',
|
expiryTime: '',
|
||||||
@@ -82,7 +158,12 @@
|
|||||||
lastNum: 1,
|
lastNum: 1,
|
||||||
emailPrefix: "",
|
emailPrefix: "",
|
||||||
emailPostfix: "",
|
emailPostfix: "",
|
||||||
|
subId: "",
|
||||||
|
tgId: "",
|
||||||
|
flow: "",
|
||||||
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
|
clients = [];
|
||||||
method=clientsBulkModal.emailMethod;
|
method=clientsBulkModal.emailMethod;
|
||||||
if(method>1){
|
if(method>1){
|
||||||
start=clientsBulkModal.firstNum;
|
start=clientsBulkModal.firstNum;
|
||||||
@@ -91,17 +172,23 @@
|
|||||||
start=0;
|
start=0;
|
||||||
end=clientsBulkModal.quantity;
|
end=clientsBulkModal.quantity;
|
||||||
}
|
}
|
||||||
prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? "_" + clientsBulkModal.emailPrefix : "";
|
prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : "";
|
||||||
useNum=(method>1);
|
useNum=(method>1);
|
||||||
postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? (method == 4 ? "@" : "") + clientsBulkModal.emailPostfix : "";
|
postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? clientsBulkModal.emailPostfix : "";
|
||||||
for (let i = start; i < end; i++) {
|
for (let i = start; i < end; i++) {
|
||||||
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
|
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
|
||||||
|
if(method==4) newClient.email = "";
|
||||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||||
|
newClient.subId = clientsBulkModal.subId;
|
||||||
|
newClient.tgId = clientsBulkModal.tgId;
|
||||||
newClient._totalGB = clientsBulkModal.totalGB;
|
newClient._totalGB = clientsBulkModal.totalGB;
|
||||||
newClient._expiryTime = clientsBulkModal.expiryTime;
|
newClient._expiryTime = clientsBulkModal.expiryTime;
|
||||||
clientsBulkModal.clients.push(newClient);
|
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
||||||
|
newClient.flow = clientsBulkModal.flow;
|
||||||
|
}
|
||||||
|
clients.push(newClient);
|
||||||
}
|
}
|
||||||
ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound);
|
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
@@ -110,16 +197,18 @@
|
|||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
this.quantity = 1;
|
this.quantity = 1;
|
||||||
this.totalGB = 0;
|
this.totalGB = 0;
|
||||||
this.expiryTime = '';
|
this.expiryTime = 0;
|
||||||
this.emailMethod= 0;
|
this.emailMethod= 0;
|
||||||
this.firstNum= 1;
|
this.firstNum= 1;
|
||||||
this.lastNum= 1;
|
this.lastNum= 1;
|
||||||
this.emailPrefix= "";
|
this.emailPrefix= "";
|
||||||
this.emailPostfix= "";
|
this.emailPostfix= "";
|
||||||
|
this.subId= "";
|
||||||
|
this.tgId= "";
|
||||||
|
this.flow= "";
|
||||||
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.delayedStart = false;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
switch(protocol){
|
switch(protocol){
|
||||||
@@ -154,6 +243,12 @@
|
|||||||
get inbound() {
|
get inbound() {
|
||||||
return this.clientsBulkModal.inbound;
|
return this.clientsBulkModal.inbound;
|
||||||
},
|
},
|
||||||
|
get delayedExpireDays() {
|
||||||
|
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0;
|
||||||
|
},
|
||||||
|
set delayedExpireDays(days){
|
||||||
|
this.clientsBulkModal.expiryTime = -86400000 * days;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,16 +12,22 @@
|
|||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
title: '',
|
title: '',
|
||||||
okText: '',
|
okText: '',
|
||||||
|
isEdit: false,
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
clients: [],
|
clients: [],
|
||||||
clientStats: [],
|
clientStats: [],
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index);
|
if(clientModal.isEdit){
|
||||||
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.index);
|
||||||
|
} else {
|
||||||
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=(index, dbInbound)=>{}, isEdit=false }) {
|
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
@@ -31,8 +37,13 @@
|
|||||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
||||||
this.index = index === null ? this.clients.length : index;
|
this.index = index === null ? this.clients.length : index;
|
||||||
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
|
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
|
||||||
|
this.delayedStart = false;
|
||||||
if (!isEdit){
|
if (!isEdit){
|
||||||
this.addClient(this.inbound.protocol, this.clients);
|
this.addClient(this.inbound.protocol, this.clients);
|
||||||
|
} else {
|
||||||
|
if (this.clients[index].expiryTime < 0){
|
||||||
|
this.delayedStart = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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;
|
||||||
@@ -81,7 +92,7 @@
|
|||||||
},
|
},
|
||||||
get isTrafficExhausted() {
|
get isTrafficExhausted() {
|
||||||
if(!clientStats) return false
|
if(!clientStats) return false
|
||||||
if(clientStats.total == 0) return false
|
if(clientStats.total <= 0) return false
|
||||||
if(clientStats.up + clientStats.down < clientStats.total) return false
|
if(clientStats.up + clientStats.down < clientStats.total) return false
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@@ -90,10 +101,16 @@
|
|||||||
},
|
},
|
||||||
get statsColor() {
|
get statsColor() {
|
||||||
if(!clientStats) return 'blue'
|
if(!clientStats) return 'blue'
|
||||||
if(clientStats.total === 0) return 'blue'
|
if(clientStats.total <= 0) return 'blue'
|
||||||
else if(clientStats.total > 0 && (clientStats.down+clientStats.up) < clientStats.total) return 'cyan'
|
else if(clientStats.total > 0 && (clientStats.down+clientStats.up) < clientStats.total) return 'cyan'
|
||||||
else return 'red'
|
else return 'red'
|
||||||
}
|
},
|
||||||
|
get delayedExpireDays() {
|
||||||
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
|
},
|
||||||
|
set delayedExpireDays(days){
|
||||||
|
this.client.expiryTime = -86400000 * days;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getNewEmail(client) {
|
getNewEmail(client) {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
</a-drawer>
|
</a-drawer>
|
||||||
<script>
|
<script>
|
||||||
const darkClass = "ant-card-dark";
|
const darkClass = "ant-card-dark";
|
||||||
const bgDarkStyle = "background-color: #1C262D";
|
const bgDarkStyle = "background-color: #242c3a";
|
||||||
const siderDrawer = {
|
const siderDrawer = {
|
||||||
visible: false,
|
visible: false,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'number'">
|
<template v-else-if="type === 'number'">
|
||||||
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
<a-input-number :value="value" @input="$emit('input', $event.target.value)"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'textarea'">
|
<template v-else-if="type === 'textarea'">
|
||||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
||||||
|
|||||||
@@ -3,73 +3,143 @@
|
|||||||
<template v-if="isEdit">
|
<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>
|
<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>
|
||||||
<a-form-item>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<span slot="label">
|
<tr>
|
||||||
Email
|
<td>{{ i18n "pages.inbounds.enable" }}</td>
|
||||||
<a-tooltip>
|
<td>
|
||||||
<template slot="title">
|
<a-form-item>
|
||||||
The email must be completely unique
|
<a-switch v-model="client.enable"></a-switch>
|
||||||
</template>
|
</a-form-item>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
</td>
|
||||||
</a-tooltip>
|
</tr>
|
||||||
</span>
|
<tr>
|
||||||
<a-input v-model.trim="client.email"></a-input>
|
<td>
|
||||||
</a-form-item>
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
<a-form-item label="password" v-if="inbound.protocol === Protocols.TROJAN">
|
<a-tooltip>
|
||||||
<a-input v-model.trim="client.password"></a-input>
|
<template slot="title">
|
||||||
</a-form-item>
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
<a-form-item label="id" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
</template>
|
||||||
<a-input v-model.trim="client.id"></a-input>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-form-item>
|
</a-tooltip>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
</td>
|
||||||
<a-input type="number" v-model.number="client.alterId"></a-input>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item v-if="inbound.xtls" label="flow">
|
<a-input v-model.trim="client.email" style="width: 250px"></a-input>
|
||||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
</a-form-item>
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
</td>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
</tr>
|
||||||
</a-select>
|
<tr v-if="inbound.protocol === Protocols.TROJAN">
|
||||||
</a-form-item>
|
<td>password</td>
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline">
|
<td>
|
||||||
<a-select v-model="client.flow" style="width: 150px">
|
<a-form-item>
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-input v-model.trim="client.password" style="width: 250px"></a-input>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
</a-form-item>
|
||||||
</a-select>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item>
|
<tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||||
<span slot="label">
|
<td>ID</td>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<td>
|
||||||
<a-tooltip>
|
<a-form-item>
|
||||||
<template slot="title">
|
<a-input v-model.trim="client.id" style="width: 250px"></a-input>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
</a-form-item>
|
||||||
</template>
|
</td>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</tr>
|
||||||
</a-tooltip>
|
<tr v-if="inbound.protocol === Protocols.VMESS">
|
||||||
</span>
|
<td>{{ i18n "additional" }}</td>
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
<td>
|
||||||
<template v-if="isEdit && clientStats">
|
<a-form-item>
|
||||||
<span>{{ i18n "usage" }}:</span>
|
<a-input-number v-model.number="client.alterId"></a-input-number>
|
||||||
<a-tag :color="statsColor">
|
</a-form-item>
|
||||||
[[ sizeFormat(clientStats.up) ]] /
|
</td>
|
||||||
[[ sizeFormat(clientStats.down) ]]
|
</tr>
|
||||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
<tr v-if="client.email">
|
||||||
</a-tag>
|
<td>Subscription</td>
|
||||||
</template>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item>
|
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
|
||||||
<span slot="label">
|
</a-form-item>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
</td>
|
||||||
<a-tooltip>
|
</tr>
|
||||||
<template slot="title">
|
<tr v-if="client.email">
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<td>Telegram Username</td>
|
||||||
</template>
|
<td>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-form-item>
|
||||||
</a-tooltip>
|
<a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
|
||||||
</span>
|
</a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
</td>
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
</tr>
|
||||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
<tr v-if="inbound.canEnableTlsFlow()">
|
||||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
<td>Flow</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="isEdit && clientStats">
|
||||||
|
<td>{{ i18n "usage" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag :color="statsColor">
|
||||||
|
[[ sizeFormat(clientStats.up) ]] /
|
||||||
|
[[ sizeFormat(clientStats.down) ]]
|
||||||
|
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||||
|
</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="clientModal.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' }" format="YYYY-MM-DD HH:mm"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
|
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,58 +1,91 @@
|
|||||||
{{define "form/inbound"}}
|
{{define "form/inbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "remark" }}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-input v-model.trim="dbInbound.remark"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td>{{ i18n "enable" }}</td>
|
||||||
<a-form-item label='{{ i18n "enable" }}'>
|
<td>
|
||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
</a-form-item>
|
||||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
</td>
|
||||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
</tr>
|
||||||
</a-select>
|
<tr>
|
||||||
</a-form-item>
|
<td>{{ i18n "remark" }}</td>
|
||||||
<a-form-item>
|
<td>
|
||||||
<span slot="label">
|
<a-form-item>
|
||||||
{{ i18n "monitor" }}
|
<a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
|
||||||
<a-tooltip>
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "protocol" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "monitor" }}
|
||||||
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</td>
|
||||||
<a-input v-model.trim="inbound.listen"></a-input>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
|
||||||
<a-input type="number" v-model.number="inbound.port"></a-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
<a-form-item>
|
</tr>
|
||||||
<span slot="label">
|
<tr>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||||
<a-tooltip>
|
<td>
|
||||||
<template slot="title">
|
<a-form-item>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
<a-input-number v-model.number="inbound.port"></a-input-number>
|
||||||
</template>
|
</a-form-item>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</td>
|
||||||
</a-tooltip>
|
</tr>
|
||||||
</span>
|
<tr>
|
||||||
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
|
<td>
|
||||||
</a-form-item>
|
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||||
<a-form-item>
|
<a-tooltip>
|
||||||
<span slot="label">
|
<template slot="title">
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
<a-tooltip>
|
</template>
|
||||||
<template slot="title">
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
</a-tooltip>
|
||||||
</template>
|
</td>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<td>
|
||||||
</a-tooltip>
|
<a-form-item>
|
||||||
</span>
|
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
</a-form-item>
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
</td>
|
||||||
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker>
|
</tr>
|
||||||
</a-form-item>
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- vmess settings -->
|
<!-- vmess settings -->
|
||||||
|
|||||||
@@ -1,20 +1,42 @@
|
|||||||
{{define "form/dokodemo"}}
|
{{define "form/dokodemo"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td>{{ i18n "pages.inbounds.targetAddress"}}</td>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
<td>
|
||||||
<a-input type="number" v-model.number="inbound.settings.port"></a-input>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
</a-form-item>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
</td>
|
||||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
</tr>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<tr>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<td>{{ i18n "pages.inbounds.destinationPort"}}</td>
|
||||||
</a-select>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item label="FollowRedirect">
|
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.network"}}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||||
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>FollowRedirect</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,10 +1,22 @@
|
|||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "username"}}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td>{{ i18n "username"}}</td>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<td>
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "password" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,19 +1,36 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<tr>
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<td>{{ i18n "encryption" }}</td>
|
||||||
</a-select>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
</a-form-item>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
</td>
|
||||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
</tr>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<tr>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<td>{{ i18n "password" }}</td>
|
||||||
</a-select>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.settings.password"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||||
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,24 +1,50 @@
|
|||||||
{{define "form/socks"}}
|
{{define "form/socks"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<!-- <a-form-item label="Password authentication">-->
|
<!-- <a-form-item label="Password authentication">-->
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
<tr>
|
||||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
<td>{{ i18n "password" }}</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||||
|
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
<a-form-item label='{{ i18n "username" }}'>
|
<tr>
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
<td>{{ i18n "username" }}</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item>
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "password" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'>
|
<tr>
|
||||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
<td>{{ i18n "pages.inbounds.enable" }} udp</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item v-if="inbound.settings.udp"
|
<a-form-item>
|
||||||
label="IP">
|
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>IP</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item v-if="inbound.settings.udp">
|
||||||
|
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -2,55 +2,98 @@
|
|||||||
<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" }}">
|
||||||
<a-form layout="inline">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-form-item>
|
<tr>
|
||||||
<span slot="label">
|
<td>
|
||||||
Email
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
The email must be completely unique
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</td>
|
||||||
<a-input v-model.trim="client.email"></a-input>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
</a-form>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
<a-form-item label="password">
|
</a-form-item>
|
||||||
<a-input v-model.trim="client.password"></a-input>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item v-if="inbound.xtls" label="flow">
|
<tr>
|
||||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<td>password</td>
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<td>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-form-item>
|
||||||
</a-select>
|
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
</td>
|
||||||
<span slot="label">
|
</tr>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<tr>
|
||||||
<a-tooltip>
|
<td>Subscription</td>
|
||||||
<template slot="title">
|
<td>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
<a-form-item v-if="client.email">
|
||||||
</template>
|
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</a-form-item>
|
||||||
</a-tooltip>
|
</td>
|
||||||
</span>
|
</tr>
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
<tr>
|
||||||
</a-form-item>
|
<td>Telegram Username</td>
|
||||||
<a-form-item>
|
<td>
|
||||||
<span slot="label">
|
<a-form-item v-if="client.email">
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||||
<a-tooltip>
|
</a-form-item>
|
||||||
<template slot="title">
|
</td>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
</tr>
|
||||||
</template>
|
<tr>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<td>
|
||||||
</a-tooltip>
|
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||||
</span>
|
<a-tooltip>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<template slot="title">
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
</template>
|
||||||
</a-form-item>
|
<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' }" format="YYYY-MM-DD HH:mm"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
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>
|
||||||
@@ -97,7 +140,7 @@
|
|||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xver">
|
<a-form-item label="xver">
|
||||||
<a-input type="number" v-model.number="fallback.xver"></a-input>
|
<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-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -2,61 +2,109 @@
|
|||||||
<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" }}">
|
||||||
<a-form layout="inline">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-form-item>
|
<tr>
|
||||||
<span slot="label">
|
<td>
|
||||||
Email
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
The email must be completely unique
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</td>
|
||||||
<a-input v-model.trim="client.email"></a-input>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
</a-form>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
<a-form-item label="id">
|
</a-form-item>
|
||||||
<a-input v-model.trim="client.id"></a-input>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item v-if="inbound.xtls" label="flow">
|
<tr>
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<td>id</td>
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<td>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-form-item>
|
||||||
</a-select>
|
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline">
|
</td>
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
</tr>
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<tr v-if="inbound.canEnableTlsFlow()">
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<td>flow</td>
|
||||||
</a-select>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item>
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<span slot="label">
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
<a-tooltip>
|
</a-select>
|
||||||
<template slot="title">
|
</a-form-item>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
</td>
|
||||||
</template>
|
</tr>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<tr>
|
||||||
</a-tooltip>
|
<td>Subscription</td>
|
||||||
</span>
|
<td>
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
<a-form-item v-if="client.email">
|
||||||
</a-form-item>
|
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<span slot="label">
|
</td>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
</tr>
|
||||||
<a-tooltip>
|
<tr>
|
||||||
<template slot="title">
|
<td>Telegram Username</td>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<td>
|
||||||
</template>
|
<a-form-item v-if="client.email">
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||||
</a-tooltip>
|
</a-form-item>
|
||||||
</span>
|
</td>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
</tr>
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
<tr>
|
||||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
<td>
|
||||||
</a-form-item>
|
<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' }" format="YYYY-MM-DD HH:mm"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
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>
|
||||||
@@ -103,7 +151,7 @@
|
|||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xver">
|
<a-form-item label="xver">
|
||||||
<a-input type="number" v-model.number="fallback.xver"></a-input>
|
<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-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -2,52 +2,106 @@
|
|||||||
<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" }}">
|
||||||
<a-form layout="inline">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-form-item>
|
<tr>
|
||||||
<span slot="label">
|
<td>
|
||||||
Email
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
The email must be completely unique
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</td>
|
||||||
<a-input v-model.trim="client.email"></a-input>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
</a-form>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
<a-form-item label="id">
|
</a-form-item>
|
||||||
<a-input v-model.trim="client.id"></a-input>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
<tr>
|
||||||
<a-input type="number" v-model.number="client.alterId"></a-input>
|
<td>id</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
</a-form-item>
|
||||||
<a-tooltip>
|
</td>
|
||||||
<template slot="title">
|
</tr>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
<tr>
|
||||||
</template>
|
<td>{{ i18n "additional" }}</td>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<td>
|
||||||
</a-tooltip>
|
<a-form-item>
|
||||||
</span>
|
<a-input-number v-model.number="client.alterId"></a-input-number>
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
<a-form-item>
|
</tr>
|
||||||
<span slot="label">
|
<tr>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<td>Subscription</td>
|
||||||
<a-tooltip>
|
<td>
|
||||||
<template slot="title">
|
<a-form-item v-if="client.email">
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||||
</template>
|
</a-form-item>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</td>
|
||||||
</a-tooltip>
|
</tr>
|
||||||
</span>
|
<tr>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<td>Telegram Username</td>
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
<td>
|
||||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
<a-form-item v-if="client.email">
|
||||||
</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' }" format="YYYY-MM-DD HH:mm"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
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>
|
||||||
@@ -64,8 +118,7 @@
|
|||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
||||||
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
<a-switch v-model="inbound.settings.disableInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="serviceName">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td>serviceName</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,12 +1,24 @@
|
|||||||
{{define "form/streamHTTP"}}
|
{{define "form/streamHTTP"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td>{{ i18n "path" }}</td>
|
||||||
<a-form-item label="host">
|
<td>
|
||||||
<a-row v-for="(host, index) in inbound.stream.http.host">
|
<a-form-item>
|
||||||
<a-input v-model.trim="inbound.stream.http.host[index]"></a-input>
|
<a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
|
||||||
</a-row>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>host</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-row v-for="(host, index) in inbound.stream.http.host">
|
||||||
|
<a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
|
||||||
|
</a-row>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,38 +1,85 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<tr>
|
||||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
<td>{{ i18n "camouflage" }}</td>
|
||||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
<td>
|
||||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
<a-form-item>
|
||||||
<a-select-option value="wechat-video">wechat-video(camouflage WeChat video)</a-select-option>
|
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="dtls">dtls(camouflage DTLS 1.2 packages)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="wireguard">wireguard(camouflage wireguard packages)</a-select-option>
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
</a-select>
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
</a-form-item>
|
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||||
<a-input v-model.number="inbound.stream.kcp.seed"></a-input>
|
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
<a-form-item label="mtu">
|
</a-form-item>
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.mtu"></a-input>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item label="tti (ms)">
|
<tr>
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.tti"></a-input>
|
<td>{{ i18n "password" }}</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item label="uplink capacity (MB/S)">
|
<a-form-item>
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.upCap"></a-input>
|
<a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="downlink capacity (MB/S)">
|
</td>
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.downCap"></a-input>
|
</tr>
|
||||||
</a-form-item>
|
<tr>
|
||||||
<a-form-item label="congestion">
|
<td>mtu</td>
|
||||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item label="read buffer size (MB)">
|
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.readBuffer"></a-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
<a-form-item label="write buffer size (MB)">
|
</tr>
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.writeBuffer"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td>tti (ms)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>uplink capacity (MB/S)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>downlink capacity (MB/S)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>congestion</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>read buffer size (MB)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>write buffer size (MB)</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,24 +1,41 @@
|
|||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<tr>
|
||||||
<a-select-option value="none">none</a-select-option>
|
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
||||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
<td>
|
||||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
<a-form-item>
|
||||||
</a-select>
|
<a-select v-model="inbound.stream.quic.security" style="width: 200px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
</a-form-item>
|
<a-select-option value="none">none</a-select-option>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
</a-form-item>
|
||||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
</td>
|
||||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
</tr>
|
||||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
<tr>
|
||||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
<td>{{ i18n "password" }}</td>
|
||||||
<a-select-option value="wechat-video">wechat-video(camouflage WeChat video)</a-select-option>
|
<td>
|
||||||
<a-select-option value="dtls">dtls(camouflage DTLS 1.2 packages)</a-select-option>
|
<a-form-item>
|
||||||
<a-select-option value="wireguard">wireguard(camouflage wireguard packages)</a-select-option>
|
<a-input v-model.trim="inbound.stream.quic.key" style="width: 200px;"></a-input>
|
||||||
</a-select>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "camouflage" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="inbound.stream.quic.type" style="width: 200px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||||
|
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||||
|
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,16 +1,24 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<tr>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<td>{{ i18n "transmission" }}</td>
|
||||||
<a-select-option value="kcp">kcp</a-select-option>
|
<td>
|
||||||
<a-select-option value="ws">ws</a-select-option>
|
<a-form-item>
|
||||||
<a-select-option value="http">http</a-select-option>
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||||
<a-select-option value="quic">quic</a-select-option>
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="grpc">grpc</a-select-option>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
</a-select>
|
<a-select-option value="kcp">kcp</a-select-option>
|
||||||
</a-form-item>
|
<a-select-option value="ws">ws</a-select-option>
|
||||||
|
<a-select-option value="http">http</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>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tcp -->
|
<!-- tcp -->
|
||||||
|
|||||||
@@ -13,74 +13,109 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tcp request -->
|
<!-- tcp request -->
|
||||||
<a-form v-if="inbound.stream.tcp.type === 'http'"
|
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
||||||
layout="inline">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
|
<tr>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
<td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}'>
|
<a-form-item>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestPath" }}'>
|
</td>
|
||||||
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
|
</tr>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input>
|
<tr>
|
||||||
</a-row>
|
<td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
<a-form-item>
|
||||||
<a-row>
|
<a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
|
||||||
<a-button size="small"
|
</a-form-item>
|
||||||
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
</td>
|
||||||
+
|
</tr>
|
||||||
</a-button>
|
<tr>
|
||||||
</a-row>
|
<td>{{ i18n "pages.inbounds.stream.tcp.requestPath" }}</td>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<td>
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-form-item>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;"></a-input>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
</a-row>
|
||||||
<template slot="addonAfter">
|
</a-form-item>
|
||||||
<a-button size="small"
|
</td>
|
||||||
@click="inbound.stream.tcp.request.removeHeader(index)">
|
</tr>
|
||||||
-
|
<tr>
|
||||||
</a-button>
|
<td colspan="2">
|
||||||
</template>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||||
</a-input>
|
<a-row>
|
||||||
</a-input-group>
|
<a-button size="small"
|
||||||
</a-form-item>
|
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
||||||
</a-form>
|
+
|
||||||
|
</a-button>
|
||||||
<!-- tcp response -->
|
</a-row>
|
||||||
<a-form v-if="inbound.stream.tcp.type === 'http'"
|
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
layout="inline">
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
|
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
</a-form-item>
|
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}'>
|
<template slot="addonAfter">
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input>
|
<a-button size="small"
|
||||||
</a-form-item>
|
@click="inbound.stream.tcp.request.removeHeader(index)">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
|
-
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
</a-button>
|
||||||
</a-form-item>
|
</template>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
</a-input>
|
||||||
<a-row>
|
</a-input-group>
|
||||||
<a-button size="small"
|
</a-form-item>
|
||||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
</td>
|
||||||
+
|
</tr>
|
||||||
</a-button>
|
<!-- tcp response -->
|
||||||
</a-row>
|
<tr>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
<td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<td>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
<a-form-item>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
</a-form-item>
|
||||||
<template slot="addonAfter">
|
</td>
|
||||||
<a-button size="small"
|
</tr>
|
||||||
@click="inbound.stream.tcp.response.removeHeader(index)">
|
<tr>
|
||||||
-
|
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
|
||||||
</a-button>
|
<td>
|
||||||
</template>
|
<a-form-item>
|
||||||
</a-input>
|
<a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
|
||||||
</a-input-group>
|
</a-form-item>
|
||||||
</a-form-item>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
||||||
|
<a-row>
|
||||||
|
<a-button size="small"
|
||||||
|
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
||||||
|
+
|
||||||
|
</a-button>
|
||||||
|
</a-row>
|
||||||
|
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.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.tcp.response.removeHeader(index)">
|
||||||
|
-
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,33 +1,47 @@
|
|||||||
{{define "form/streamWS"}}
|
{{define "form/streamWS"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="acceptProxyProtocol">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
<tr>
|
||||||
</a-form-item>
|
<td>acceptProxyProtocol</td>
|
||||||
</a-form>
|
<td>
|
||||||
<a-form layout="inline">
|
<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>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
</tr>
|
||||||
<a-row>
|
<tr>
|
||||||
<a-button size="small"
|
<td>{{ i18n "path" }}</td>
|
||||||
@click="inbound.stream.ws.addHeader('Host', '')">
|
<td>
|
||||||
+
|
<a-form-item>
|
||||||
</a-button>
|
<a-input v-model.trim="inbound.stream.ws.path" style="width: 250px;"></a-input>
|
||||||
</a-row>
|
</a-form-item>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
</td>
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
</tr>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
|
<tr>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<td colspan="2">
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||||
<template slot="addonAfter">
|
<a-row>
|
||||||
<a-button size="small"
|
<a-button size="small"
|
||||||
@click="inbound.stream.ws.removeHeader(index)">
|
@click="inbound.stream.ws.addHeader('Host', '')">
|
||||||
-
|
+
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</a-row>
|
||||||
</a-input>
|
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
</a-input-group>
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
</a-form-item>
|
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,72 +1,241 @@
|
|||||||
{{define "form/tlsSettings"}}
|
{{define "form/tlsSettings"}}
|
||||||
<!-- tls enable -->
|
<!-- tls enable -->
|
||||||
<a-form layout="inline" v-if="inbound.canSetTls()">
|
<a-form v-if="inbound.canSetTls()" layout="inline">
|
||||||
<a-form-item label="tls">
|
<a-form-item label="TLS">
|
||||||
<a-switch v-model="inbound.tls">
|
<a-switch v-model="inbound.tls">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.canEnableXTls()" label="xtls">
|
<a-form-item v-if="inbound.canEnableReality()" label="Reality">
|
||||||
<a-switch v-model="inbound.xtls"></a-switch>
|
<a-switch v-model="inbound.reality"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<a-form v-if="inbound.tls || inbound.xtls" layout="inline">
|
<a-form v-if="inbound.tls" layout="inline">
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input>
|
<tr>
|
||||||
</a-form-item>
|
<td>SNI</td>
|
||||||
<a-form-item label="CipherSuites">
|
<td>
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
|
<a-form-item placeholder="Server Name Indication" v-if="inbound.tls">
|
||||||
<a-select-option value="">auto</a-select-option>
|
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
|
||||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
</a-form-item>
|
||||||
</a-select>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item label="MinVersion">
|
<tr>
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<td>CipherSuites</td>
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<td>
|
||||||
</a-select>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-form-item label="MaxVersion">
|
<a-select-option value="">auto</a-select-option>
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select-option v-for="key in TLS_CIPHER_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-form-item>
|
</td>
|
||||||
<a-form-item label="uTLS" v-if="inbound.tls" >
|
</tr>
|
||||||
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px">
|
<tr>
|
||||||
<a-select-option value=''>None</a-select-option>
|
<td>MinVersion</td>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<td>
|
||||||
</a-select>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
<a-input v-model.trim="inbound.stream.tls.server"></a-input>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Alpn" v-if="inbound.tls">
|
</td>
|
||||||
<a-select v-model="inbound.stream.tls.alpn[0]" style="width:200px">
|
</tr>
|
||||||
<a-select-option value=''>auto</a-select-option>
|
<tr>
|
||||||
<a-select-option v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-select-option>
|
<td>MaxVersion</td>
|
||||||
</a-select>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
</a-select>
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
</a-form-item>
|
||||||
</a-radio-group>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<template v-if="inbound.stream.tls.certs[0].useFile">
|
<tr>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<td>uTLS</td>
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:300px;"></a-input>
|
<td>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px">
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input>
|
<a-select-option value=''>None</a-select-option>
|
||||||
</a-form-item>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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-checkbox-group v-model="inbound.stream.tls.alpn">
|
||||||
|
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</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 colspan="2">
|
||||||
|
<a-form-item label="{{ i18n "certificate" }}">
|
||||||
|
<a-radio-group v-model="inbound.stream.tls.certs[0].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-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-if="inbound.stream.tls.certs[0].useFile">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.tls.certs[0].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="inbound.stream.tls.certs[0].keyFile" style="width:250px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<tr>
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
|
<td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
|
||||||
</a-form-item>
|
<td>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<a-form-item>
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
|
<a-input type="textarea" :rows="3" style="width:250px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
|
||||||
</a-form-item>
|
</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="inbound.stream.tls.certs[0].key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
</table>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- reality settings -->
|
||||||
|
<a-form v-if="inbound.reality" layout="inline">
|
||||||
|
<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>
|
||||||
|
<td>Show</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="inbound.stream.reality.show"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Xver</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>uTLS</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item >
|
||||||
|
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 250px">
|
||||||
|
<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>Dest</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Server Names</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Short Ids</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>SpiderX</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Private Key</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Public Key</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -21,9 +21,12 @@
|
|||||||
<a-icon style="font-size: 24px;" type="delete" v-if="isRemovable(record.id)" @click="delClient(record.id,client)"></a-icon>
|
<a-icon style="font-size: 24px;" type="delete" v-if="isRemovable(record.id)" @click="delClient(record.id,client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="enable" slot-scope="text, client, index">
|
||||||
|
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
||||||
|
</template>
|
||||||
<template slot="client" slot-scope="text, client">
|
<template slot="client" slot-scope="text, client">
|
||||||
[[ client.email ]]
|
[[ client.email ]]
|
||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "disabled" }}</a-tag>
|
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-tag color="blue">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
|
||||||
@@ -34,11 +37,12 @@
|
|||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, client, index">
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
<template v-if="client._expiryTime > 0">
|
<template v-if="client.expiryTime > 0">
|
||||||
<a-tag :color="isExpiry(record, index)? 'red' : 'blue'">
|
<a-tag :color="isExpiry(record, index)? 'red' : 'blue'">
|
||||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
<a-tag v-else-if="client.expiryTime < 0" color="cyan">[[ client._expiryTime ]] {{ i18n "pages.client.days" }}</a-tag>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -49,9 +49,9 @@
|
|||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td v-else-if="inbound.xtls">
|
<td v-else-if="inbound.reality">
|
||||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
@@ -59,15 +59,25 @@
|
|||||||
</table>
|
</table>
|
||||||
<template v-if="infoModal.clientSettings">
|
<template v-if="infoModal.clientSettings">
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px;">
|
||||||
<tr>
|
<tr v-for="col,index in Object.keys(infoModal.clientSettings).slice(0, 3)">
|
||||||
<th v-for="col in Object.keys(infoModal.clientSettings).slice(0, 3)">[[ col ]]</th>
|
<td>[[ col ]]</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings[col] ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td v-for="col in Object.values(infoModal.clientSettings).slice(0, 3)"><a-tag color="green">[[ col ]]</a-tag></td>
|
<td>{{ i18n "status" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
||||||
|
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
|
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr><th>{{ i18n "usage" }}</th><th>{{ i18n "pages.inbounds.totalFlow" }}</th><th>{{ i18n "pages.inbounds.expireDate" }}</th><th>{{ i18n "enable" }}</th></tr>
|
<tr>
|
||||||
|
<th>{{ i18n "usage" }}</th>
|
||||||
|
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
||||||
|
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientStats" :color="statsColor(infoModal.clientStats)">
|
<a-tag v-if="infoModal.clientStats" :color="statsColor(infoModal.clientStats)">
|
||||||
@@ -86,12 +96,19 @@
|
|||||||
[[ 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 color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
</tr>
|
||||||
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
</table>
|
||||||
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
<table v-if="infoModal.clientSettings.subId + infoModal.clientSettings.tgId" style="margin-bottom: 10px;">
|
||||||
</td>
|
<tr v-if="infoModal.clientSettings.subId">
|
||||||
|
<td>Subscription link</td>
|
||||||
|
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientSettings.tgId">
|
||||||
|
<td>Telegram Username</td>
|
||||||
|
<td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
@@ -162,13 +179,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const infoModal = {
|
const infoModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
settings: null,
|
settings: null,
|
||||||
clientSettings: new Inbound.Settings(),
|
clientSettings: null,
|
||||||
clientStats: [],
|
clientStats: [],
|
||||||
upStats: 0,
|
upStats: 0,
|
||||||
downStats: 0,
|
downStats: 0,
|
||||||
@@ -211,12 +227,24 @@
|
|||||||
get inbound() {
|
get inbound() {
|
||||||
return this.infoModal.inbound;
|
return this.infoModal.inbound;
|
||||||
},
|
},
|
||||||
get isEnable() {
|
get isActive() {
|
||||||
if(infoModal.clientStats){
|
if(infoModal.clientStats){
|
||||||
return infoModal.clientStats.enable;
|
return infoModal.clientStats.enable;
|
||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return infoModal.dbInbound.isEnable;
|
||||||
}
|
},
|
||||||
|
get isEnable() {
|
||||||
|
if(infoModal.clientSettings){
|
||||||
|
return infoModal.clientSettings.enable;
|
||||||
|
}
|
||||||
|
return infoModal.dbInbound.isEnable;
|
||||||
|
},
|
||||||
|
get subBase() {
|
||||||
|
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + basePath + "sub/";
|
||||||
|
},
|
||||||
|
get tgBase() {
|
||||||
|
return "https://t.me/"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyTextToClipboard(elmentId,content) {
|
copyTextToClipboard(elmentId,content) {
|
||||||
|
|||||||
@@ -43,6 +43,14 @@
|
|||||||
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;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const protocols = {
|
const protocols = {
|
||||||
@@ -62,6 +70,7 @@
|
|||||||
inModal: inModal,
|
inModal: inModal,
|
||||||
Protocols: protocols,
|
Protocols: protocols,
|
||||||
SSMethods: SSMethods,
|
SSMethods: SSMethods,
|
||||||
|
delayedStart: false,
|
||||||
get inbound() {
|
get inbound() {
|
||||||
return inModal.inbound;
|
return inModal.inbound;
|
||||||
},
|
},
|
||||||
@@ -70,31 +79,39 @@
|
|||||||
},
|
},
|
||||||
get isEdit() {
|
get isEdit() {
|
||||||
return inModal.isEdit;
|
return inModal.isEdit;
|
||||||
}
|
},
|
||||||
|
get client() {
|
||||||
|
return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0];
|
||||||
|
},
|
||||||
|
get delayedExpireDays() {
|
||||||
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
|
},
|
||||||
|
set delayedExpireDays(days){
|
||||||
|
this.client.expiryTime = -86400000 * days;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange(oldValue) {
|
streamNetworkChange() {
|
||||||
if (oldValue === 'kcp') {
|
if (!inModal.inbound.canSetTls()) {
|
||||||
this.inModal.inbound.tls = false;
|
this.inModal.inbound.stream.security = 'none';
|
||||||
|
}
|
||||||
|
if (!inModal.inbound.canEnableReality()) {
|
||||||
|
this.inModal.inbound.reality = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addClient(protocol, clients) {
|
setDefaultCertData(){
|
||||||
switch (protocol) {
|
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
},
|
||||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
async getNewX25519Cert(){
|
||||||
default: return null;
|
inModal.loading(true);
|
||||||
|
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||||
|
inModal.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||||
removeClient(index, clients) {
|
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||||
clients.splice(index, 1);
|
|
||||||
},
|
|
||||||
isExpiry(index) {
|
|
||||||
return this.inbound.isExpiry(index)
|
|
||||||
},
|
|
||||||
isClientEnable(email) {
|
|
||||||
clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null
|
|
||||||
return clientStats ? clientStats['enable'] : true
|
|
||||||
},
|
},
|
||||||
getNewEmail(client) {
|
getNewEmail(client) {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||||
|
|||||||
@@ -11,10 +11,6 @@
|
|||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-row-expand-icon {
|
|
||||||
color: rgba(0,0,0,.65);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
@@ -45,8 +41,24 @@
|
|||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "clients" }}:
|
{{ i18n "clients" }}:
|
||||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
<a-tag color="green">[[ total.clients ]]</a-tag>
|
||||||
<a-tag color="blue">{{ i18n "enabled" }} [[ total.active ]]</a-tag>
|
<a-popover title="{{ i18n "disabled" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<a-tag color="red">{{ i18n "disabled" }} [[ total.deactive ]]</a-tag>
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title="{{ i18n "depleted" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title="{{ i18n "depletingSoon" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -56,6 +68,7 @@
|
|||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||||
<a-button type="primary" icon="export" @click="exportAllLinks">{{ i18n "pages.inbounds.export" }}</a-button>
|
<a-button type="primary" icon="export" @click="exportAllLinks">{{ i18n "pages.inbounds.export" }}</a-button>
|
||||||
|
<a-button type="primary" icon="reload" @click="resetAllTraffic">{{ i18n "pages.inbounds.resetAllTraffic" }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
@@ -67,7 +80,7 @@
|
|||||||
<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="menu"></a-icon>
|
||||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme" style="border: 1px solid rgba(255, 255, 255, 0.65);">
|
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
|
||||||
<a-menu-item v-if="dbInbound.isSS" key="qrcode">
|
<a-menu-item v-if="dbInbound.isSS" key="qrcode">
|
||||||
<a-icon type="qrcode"></a-icon>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
@@ -78,13 +91,17 @@
|
|||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess">
|
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess">
|
||||||
<a-menu-item key="addClient">
|
<a-menu-item key="addClient">
|
||||||
<a-icon type="user"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="addBulkClient">
|
<a-menu-item key="addBulkClient">
|
||||||
<a-icon type="team"></a-icon>
|
<a-icon type="usergroup-add"></a-icon>
|
||||||
{{ i18n "pages.client.bulk"}}
|
{{ i18n "pages.client.bulk"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="resetClients">
|
||||||
|
<a-icon type="file-done"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetAllClientTraffics"}}
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item key="export">
|
<a-menu-item key="export">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export"}}
|
{{ i18n "pages.inbounds.export"}}
|
||||||
@@ -99,6 +116,9 @@
|
|||||||
<a-menu-item key="resetTraffic">
|
<a-menu-item key="resetTraffic">
|
||||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="clone">
|
||||||
|
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.Clone"}}
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item key="delete">
|
<a-menu-item key="delete">
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
@@ -108,7 +128,35 @@
|
|||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
<template slot="protocol" slot-scope="text, dbInbound">
|
||||||
<a-tag color="blue">[[ dbInbound.protocol ]]</a-tag>
|
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
||||||
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
|
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">tls</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">reality</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
|
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
|
<a-popover title="{{ i18n "disabled" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
|
<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="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
|
<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="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]] / [[ sizeFormat(dbInbound.down) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]] / [[ sizeFormat(dbInbound.down) ]]</a-tag>
|
||||||
@@ -118,14 +166,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="stream" slot-scope="text, dbInbound, index">
|
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
|
||||||
<a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
|
|
||||||
<a-tag v-if="inbounds[index].stream.isTls" color="blue">tls</a-tag>
|
|
||||||
<a-tag v-if="inbounds[index].stream.isXTls" color="blue">xtls</a-tag>
|
|
||||||
</template>
|
|
||||||
<template v-else>{{ i18n "none" }}</template>
|
|
||||||
</template>
|
|
||||||
<template slot="enable" slot-scope="text, dbInbound">
|
<template slot="enable" slot-scope="text, dbInbound">
|
||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
@@ -169,11 +209,10 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 30,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'action' },
|
scopedSlots: { customRender: 'action' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.enable" }}',
|
title: '{{ i18n "pages.inbounds.enable" }}',
|
||||||
@@ -181,35 +220,35 @@
|
|||||||
width: 40,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'enable' },
|
scopedSlots: { customRender: 'enable' },
|
||||||
}, {
|
}, {
|
||||||
title: "Id",
|
title: "ID",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
width: 30,
|
width: 30,
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.remark" }}',
|
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 60,
|
width: 50,
|
||||||
dataIndex: "remark",
|
dataIndex: "remark",
|
||||||
}, {
|
|
||||||
title: '{{ i18n "pages.inbounds.protocol" }}',
|
|
||||||
align: 'center',
|
|
||||||
width: 40,
|
|
||||||
scopedSlots: { customRender: 'protocol' },
|
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.port" }}',
|
title: '{{ i18n "pages.inbounds.port" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
dataIndex: "port",
|
dataIndex: "port",
|
||||||
|
width: 40,
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.protocol" }}',
|
||||||
|
align: 'left',
|
||||||
|
width: 70,
|
||||||
|
scopedSlots: { customRender: 'protocol' },
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "clients" }}',
|
||||||
|
align: 'left',
|
||||||
width: 50,
|
width: 50,
|
||||||
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 120,
|
width: 120,
|
||||||
scopedSlots: { customRender: 'traffic' },
|
scopedSlots: { customRender: 'traffic' },
|
||||||
}, {
|
|
||||||
title: '{{ i18n "pages.inbounds.transportConfig" }}',
|
|
||||||
align: 'center',
|
|
||||||
width: 60,
|
|
||||||
scopedSlots: { customRender: 'stream' },
|
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@@ -218,7 +257,8 @@
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
const innerColumns = [
|
const innerColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 80, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 70, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 70, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
@@ -226,7 +266,8 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerTrojanColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 80, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 70, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 70, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
@@ -243,6 +284,11 @@
|
|||||||
dbInbounds: [],
|
dbInbounds: [],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
searchedInbounds: [],
|
searchedInbounds: [],
|
||||||
|
expireDiff: 0,
|
||||||
|
trafficDiff: 0,
|
||||||
|
defaultCert: '',
|
||||||
|
defaultKey: '',
|
||||||
|
clientCount: {},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning=true) {
|
loading(spinning=true) {
|
||||||
@@ -258,17 +304,66 @@
|
|||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
this.searchKey = '';
|
this.searchKey = '';
|
||||||
},
|
},
|
||||||
|
async getDefaultSettings() {
|
||||||
|
this.loading();
|
||||||
|
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.expireDiff = msg.obj.expireDiff * 86400000;
|
||||||
|
this.trafficDiff = msg.obj.trafficDiff * 1073741824;
|
||||||
|
this.defaultCert = msg.obj.defaultCert;
|
||||||
|
this.defaultKey = msg.obj.defaultKey;
|
||||||
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
this.inbounds.splice(0);
|
this.inbounds.splice(0);
|
||||||
this.dbInbounds.splice(0);
|
this.dbInbounds.splice(0);
|
||||||
this.searchedInbounds.splice(0);
|
this.searchedInbounds.splice(0);
|
||||||
for (const inbound of dbInbounds) {
|
for (const inbound of dbInbounds) {
|
||||||
const dbInbound = new DBInbound(inbound);
|
const dbInbound = new DBInbound(inbound);
|
||||||
this.inbounds.push(dbInbound.toInbound());
|
to_inbound = dbInbound.toInbound()
|
||||||
|
this.inbounds.push(to_inbound);
|
||||||
this.dbInbounds.push(dbInbound);
|
this.dbInbounds.push(dbInbound);
|
||||||
this.searchedInbounds.push(dbInbound);
|
this.searchedInbounds.push(dbInbound);
|
||||||
|
if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){
|
||||||
|
this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getClientCounts(dbInbound,inbound){
|
||||||
|
let clientCount = 0,active = [], deactive = [], depleted = [], expiring = [];
|
||||||
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
|
clientStats = dbInbound.clientStats
|
||||||
|
now = new Date().getTime()
|
||||||
|
if(clients){
|
||||||
|
clientCount = clients.length;
|
||||||
|
if(dbInbound.enable){
|
||||||
|
clients.forEach(client => {
|
||||||
|
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||||
|
});
|
||||||
|
clientStats.forEach(client => {
|
||||||
|
if(!client.enable) {
|
||||||
|
depleted.push(client.email);
|
||||||
|
} else {
|
||||||
|
if ((client.expiryTime > 0 && (client.expiryTime-now < this.expireDiff)) ||
|
||||||
|
(client.total > 0 && (client.total-(client.up+client.down) < this.trafficDiff ))) expiring.push(client.email);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clients.forEach(client => {
|
||||||
|
deactive.push(client.email);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
clients: clientCount,
|
||||||
|
active: active,
|
||||||
|
deactive: deactive,
|
||||||
|
depleted: depleted,
|
||||||
|
expiring: expiring,
|
||||||
|
};
|
||||||
|
},
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
if (ObjectUtil.isEmpty(key)) {
|
if (ObjectUtil.isEmpty(key)) {
|
||||||
this.searchedInbounds = this.dbInbounds.slice();
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
@@ -315,6 +410,12 @@
|
|||||||
case "resetTraffic":
|
case "resetTraffic":
|
||||||
this.resetTraffic(dbInbound.id);
|
this.resetTraffic(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
case "resetClients":
|
||||||
|
this.resetAllClientTraffics(dbInbound.id);
|
||||||
|
break;
|
||||||
|
case "clone":
|
||||||
|
this.openCloneInbound(dbInbound);
|
||||||
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
this.delInbound(dbInbound.id);
|
this.delInbound(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
@@ -349,6 +450,39 @@
|
|||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
openCloneInbound(dbInbound) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.cloneInbound"}} ' + dbInbound.remark,
|
||||||
|
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||||
|
okText: '{{ i18n "pages.inbounds.revise"}}',
|
||||||
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
|
onOk: () => {
|
||||||
|
const baseInbound = dbInbound.toInbound();
|
||||||
|
dbInbound.up = 0;
|
||||||
|
dbInbound.down = 0;
|
||||||
|
this.cloneInbound(baseInbound, dbInbound);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async cloneInbound(baseInbound, dbInbound) {
|
||||||
|
const inbound = new Inbound();
|
||||||
|
const data = {
|
||||||
|
up: dbInbound.up,
|
||||||
|
down: dbInbound.down,
|
||||||
|
total: dbInbound.total,
|
||||||
|
remark: dbInbound.remark + " - Cloned",
|
||||||
|
enable: dbInbound.enable,
|
||||||
|
expiryTime: dbInbound.expiryTime,
|
||||||
|
|
||||||
|
listen: inbound.listen,
|
||||||
|
port: inbound.port,
|
||||||
|
protocol: baseInbound.protocol,
|
||||||
|
settings: inbound.settings.toString(),
|
||||||
|
streamSettings: baseInbound.stream.toString(),
|
||||||
|
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
||||||
|
};
|
||||||
|
await this.submit('/xui/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
async addInbound(inbound, dbInbound) {
|
async addInbound(inbound, dbInbound) {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -394,9 +528,9 @@
|
|||||||
title: '{{ i18n "pages.client.add"}}',
|
title: '{{ i18n "pages.client.add"}}',
|
||||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound, index) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientModal.loading();
|
clientModal.loading();
|
||||||
await this.addClient(inbound, dbInbound);
|
await this.addClient(clients, dbInboundId);
|
||||||
clientModal.close();
|
clientModal.close();
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
@@ -408,9 +542,9 @@
|
|||||||
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
||||||
okText: '{{ i18n "pages.client.bulk"}}',
|
okText: '{{ i18n "pages.client.bulk"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientsBulkModal.loading();
|
clientsBulkModal.loading();
|
||||||
await this.addClient(inbound, dbInbound);
|
await this.addClient(clients, dbInboundId);
|
||||||
clientsBulkModal.close();
|
clientsBulkModal.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -424,9 +558,9 @@
|
|||||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
index: index,
|
index: index,
|
||||||
confirm: async (inbound, dbInbound, index) => {
|
confirm: async (client, dbInboundId, index) => {
|
||||||
clientModal.loading();
|
clientModal.loading();
|
||||||
await this.updateClient(inbound, dbInbound, index);
|
await this.updateClient(client, dbInboundId, index);
|
||||||
clientModal.close();
|
clientModal.close();
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
@@ -436,17 +570,17 @@
|
|||||||
firstKey = Object.keys(client)[0];
|
firstKey = Object.keys(client)[0];
|
||||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||||
},
|
},
|
||||||
async addClient(inbound, dbInbound) {
|
async addClient(clients, dbInboundId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInbound.id,
|
id: dbInboundId,
|
||||||
settings: inbound.settings.toString(),
|
settings: '{"clients": [' + clients.toString() +']}',
|
||||||
};
|
};
|
||||||
await this.submit('/xui/inbound/addClient', data);
|
await this.submit(`/xui/inbound/addClient`, data);
|
||||||
},
|
},
|
||||||
async updateClient(inbound, dbInbound, index) {
|
async updateClient(client, dbInboundId, index) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInbound.id,
|
id: dbInboundId,
|
||||||
settings: inbound.settings.toString(),
|
settings: '{"clients": [' + client.toString() +']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
||||||
},
|
},
|
||||||
@@ -515,6 +649,16 @@
|
|||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
|
this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
|
||||||
},
|
},
|
||||||
|
async switchEnableClient(dbInboundId, client) {
|
||||||
|
this.loading()
|
||||||
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
inbound = dbInbound.toInbound();
|
||||||
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
|
index = this.findIndexOfClient(clients, client);
|
||||||
|
clients[index].enable = ! clients[index].enable
|
||||||
|
await this.updateClient(inbound, dbInbound, index);
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
async submit(url, data) {
|
async submit(url, data) {
|
||||||
const msg = await HttpUtil.postWithModal(url, data);
|
const msg = await HttpUtil.postWithModal(url, data);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
@@ -540,6 +684,26 @@
|
|||||||
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ client.email),
|
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ client.email),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
resetAllTraffic() {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
||||||
|
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
|
okText: '{{ i18n "reset"}}',
|
||||||
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
|
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetAllClientTraffics(dbInboundId) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||||
|
content: '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
|
okText: '{{ i18n "reset"}}',
|
||||||
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
|
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
|
})
|
||||||
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(index)
|
return dbInbound.toInbound().isExpiry(index)
|
||||||
},
|
},
|
||||||
@@ -583,37 +747,30 @@
|
|||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.getDefaultSettings();
|
||||||
this.getDBInbounds();
|
this.getDBInbounds();
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
total() {
|
total() {
|
||||||
let down = 0, up = 0;
|
let down = 0, up = 0;
|
||||||
let clients = 0, active = 0, deactive = 0;
|
let clients = 0, deactive = [], depleted = [], expiring = [];
|
||||||
this.dbInbounds.forEach(dbInbound => {
|
this.dbInbounds.forEach(dbInbound => {
|
||||||
down += dbInbound.down;
|
down += dbInbound.down;
|
||||||
up += dbInbound.up;
|
up += dbInbound.up;
|
||||||
inbound = dbInbound.toInbound();
|
if (this.clientCount[dbInbound.id]) {
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients += this.clientCount[dbInbound.id].clients;
|
||||||
if(clients){
|
deactive = deactive.concat(this.clientCount[dbInbound.id].deactive);
|
||||||
if(dbInbound.enable){
|
depleted = depleted.concat(this.clientCount[dbInbound.id].depleted);
|
||||||
isClientEnable = false;
|
expiring = expiring.concat(this.clientCount[dbInbound.id].expiring);
|
||||||
clients.forEach(client => {
|
|
||||||
isClientEnable = client.email == "" ? true: this.isClientEnabled(dbInbound,client.email);
|
|
||||||
isClientEnable ? active++ : deactive++;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
deactive += clients.length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbInbound.enable ? active++ : deactive++;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
down: down,
|
down: down,
|
||||||
up: up,
|
up: up,
|
||||||
clients: active + deactive,
|
clients: clients,
|
||||||
active: active,
|
|
||||||
deactive: deactive,
|
deactive: deactive,
|
||||||
|
depleted: depleted,
|
||||||
|
expiring: expiring,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -74,6 +74,24 @@
|
|||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
|
x-ui: <a-tag color="green">{{ .cur_ver }}</a-tag>
|
||||||
|
xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
|
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.operationHoursDesc" }}
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
{{ i18n "pages.index.xrayStatus" }}:
|
||||||
@@ -84,22 +102,17 @@
|
|||||||
</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" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||||
<a-tag color="blue" @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="blue" @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="blue" @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="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="#87d068">[[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Logs</a-tag>
|
||||||
<a-tooltip>
|
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
|
||||||
<template slot="title">
|
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
|
||||||
{{ i18n "pages.index.operationHoursDesc" }}
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
@@ -177,7 +190,7 @@
|
|||||||
<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="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
ok-text='{{ i18n "confirm" }}' cancel-text='{{ i18n "cancel"}}'>
|
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">
|
||||||
@@ -187,8 +200,39 @@
|
|||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
||||||
|
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
|
width="800px"
|
||||||
|
footer="">
|
||||||
|
<a-form layout="inline">
|
||||||
|
<a-form-item label="Count">
|
||||||
|
<a-select v-model="logModal.rows"
|
||||||
|
style="width: 80px"
|
||||||
|
@change="openLogs(logModal.rows)"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option value="10">10</a-select-option>
|
||||||
|
<a-select-option value="20">20</a-select-option>
|
||||||
|
<a-select-option value="50">50</a-select-option>
|
||||||
|
<a-select-option value="100">100</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<button class="ant-btn ant-btn-primary" @click="openLogs(logModal.rows)"><a-icon type="sync"></a-icon> Reload</button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-button type="primary" style="margin-bottom: 10px;"
|
||||||
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" download="x-ui.log">
|
||||||
|
{{ i18n "download" }} x-ui.log
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-input type="textarea" v-model="logModal.logs" disabled="true"
|
||||||
|
:autosize="{ minRows: 10, maxRows: 22}"></a-input>
|
||||||
|
</a-modal>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
{{template "textModal"}}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
@@ -280,6 +324,20 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const logModal = {
|
||||||
|
visible: false,
|
||||||
|
logs: '',
|
||||||
|
rows: 20,
|
||||||
|
show(logs, rows) {
|
||||||
|
this.visible = true;
|
||||||
|
this.rows = rows;
|
||||||
|
this.logs = logs.join("\n");
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -287,6 +345,7 @@
|
|||||||
siderDrawer,
|
siderDrawer,
|
||||||
status: new Status(),
|
status: new Status(),
|
||||||
versionModal,
|
versionModal,
|
||||||
|
logModal,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
},
|
},
|
||||||
@@ -346,6 +405,27 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async openLogs(rows){
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('server/logs/'+rows);
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logModal.show(msg.obj,rows);
|
||||||
|
},
|
||||||
|
async openConfig(){
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('server/getConfigJson');
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
txtModal.show('config.json',JSON.stringify(msg.obj, null, 2),'config.json');
|
||||||
|
},
|
||||||
|
getBackup(){
|
||||||
|
window.location = basePath + 'server/getDb';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
||||||
|
<setting-list-item type="number" title='{{ i18n "pages.setting.expireTimeDiff" }}' desc='{{ i18n "pages.setting.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
||||||
|
<setting-list-item type="number" title='{{ i18n "pages.setting.trafficDiff" }}' desc='{{ i18n "pages.setting.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
@@ -117,11 +119,9 @@
|
|||||||
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramToken"}}' desc='{{ i18n "pages.setting.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramToken"}}' desc='{{ i18n "pages.setting.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}' v-model.number="allSetting.tgBotChatId"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramNotifyTime"}}' desc='{{ i18n "pages.setting.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramNotifyTime"}}' desc='{{ i18n "pages.setting.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.tgNotifyBackup" }}' desc='{{ i18n "pages.setting.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.tgNotifyBackup" }}' desc='{{ i18n "pages.setting.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyExpireTimeDiff" }}' desc='{{ i18n "pages.setting.tgNotifyExpireTimeDiffDesc" }}' v-model="allSetting.tgExpireDiff" :min="0"></setting-list-item>
|
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyTrafficDiff" }}' desc='{{ i18n "pages.setting.tgNotifyTrafficDiffDesc" }}' v-model="allSetting.tgTrafficDiff" :min="0"></setting-list-item>
|
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|||||||
@@ -64,28 +64,45 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
|
|||||||
return clients, nil
|
return clients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) {
|
func (s *InboundService) getAllEmails() ([]string, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var emails []string
|
||||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan})
|
err := db.Raw(`
|
||||||
if ignoreId > 0 {
|
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||||
db = db.Where("id != ?", ignoreId)
|
FROM inbounds,
|
||||||
}
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
db = db.Find(&inbounds)
|
`).Scan(&emails).Error
|
||||||
if db.Error != nil {
|
|
||||||
return "", db.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, inbound := range inbounds {
|
if err != nil {
|
||||||
clients, err := s.getClients(inbound)
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return "", err
|
return emails, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) contains(slice []string, str string) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == str {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for _, client := range clients {
|
func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
|
||||||
if emails[client.Email] {
|
allEmails, err := s.getAllEmails()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var emails []string
|
||||||
|
for _, client := range clients {
|
||||||
|
if client.Email != "" {
|
||||||
|
if s.contains(emails, client.Email) {
|
||||||
return client.Email, nil
|
return client.Email, nil
|
||||||
}
|
}
|
||||||
|
if s.contains(allEmails, client.Email) {
|
||||||
|
return client.Email, nil
|
||||||
|
}
|
||||||
|
emails = append(emails, client.Email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -96,16 +113,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
emails := make(map[string]bool)
|
allEmails, err := s.getAllEmails()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var emails []string
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Email != "" {
|
if client.Email != "" {
|
||||||
if emails[client.Email] {
|
if s.contains(emails, client.Email) {
|
||||||
return client.Email, nil
|
return client.Email, nil
|
||||||
}
|
}
|
||||||
emails[client.Email] = true
|
if s.contains(allEmails, client.Email) {
|
||||||
|
return client.Email, nil
|
||||||
|
}
|
||||||
|
emails = append(emails, client.Email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.checkEmailsExist(emails, inbound.Id)
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
|
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
|
||||||
@@ -201,14 +225,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
return inbound, common.NewError("Port already exists:", inbound.Port)
|
return inbound, common.NewError("Port already exists:", inbound.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
|
||||||
if err != nil {
|
|
||||||
return inbound, err
|
|
||||||
}
|
|
||||||
if existEmail != "" {
|
|
||||||
return inbound, common.NewError("Duplicate email:", existEmail)
|
|
||||||
}
|
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
oldInbound, err := s.GetInbound(inbound.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, err
|
return inbound, err
|
||||||
@@ -231,8 +247,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
return inbound, db.Save(oldInbound).Error
|
return inbound, db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
clients, err := s.getClients(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -241,29 +261,35 @@ func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
|||||||
return common.NewError("Duplicate email:", existEmail)
|
return common.NewError("Duplicate email:", existEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, err := s.getClients(inbound)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients, err := s.getClients(oldInbound)
|
oldClients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for _, client := range clients {
|
||||||
|
newClients = append(newClients, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["clients"] = append(oldClients, newClients...)
|
||||||
|
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = inbound.Settings
|
oldInbound.Settings = string(newSettings)
|
||||||
|
|
||||||
if len(clients[len(clients)-1].Email) > 0 {
|
for _, client := range clients {
|
||||||
s.AddClientStat(inbound.Id, &clients[len(clients)-1])
|
if len(client.Email) > 0 {
|
||||||
}
|
s.AddClientStat(data.Id, &client)
|
||||||
for i := len(oldClients); i < len(clients); i++ {
|
|
||||||
if len(clients[i].Email) > 0 {
|
|
||||||
s.AddClientStat(inbound.Id, &clients[i])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
@@ -289,21 +315,13 @@ func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string)
|
|||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error {
|
func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
clients, err := s.getClients(data)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if existEmail != "" {
|
|
||||||
return common.NewError("Duplicate email:", existEmail)
|
|
||||||
}
|
|
||||||
|
|
||||||
clients, err := s.getClients(inbound)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -313,18 +331,45 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = inbound.Settings
|
if len(clients[0].Email) > 0 && clients[0].Email != oldClients[index].Email {
|
||||||
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existEmail != "" {
|
||||||
|
return common.NewError("Duplicate email:", existEmail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsClients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
newClients = append(newClients, clients[0])
|
||||||
|
settingsClients[index] = newClients[0]
|
||||||
|
|
||||||
|
settings["clients"] = settingsClients
|
||||||
|
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbound.Settings = string(newSettings)
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
if len(clients[index].Email) > 0 {
|
if len(clients[0].Email) > 0 {
|
||||||
if len(oldClients[index].Email) > 0 {
|
if len(oldClients[index].Email) > 0 {
|
||||||
err = s.UpdateClientStat(oldClients[index].Email, &clients[index])
|
err = s.UpdateClientStat(oldClients[index].Email, &clients[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.AddClientStat(inbound.Id, &clients[index])
|
s.AddClientStat(data.Id, &clients[0])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = s.DelClientStat(db, oldClients[index].Email)
|
err = s.DelClientStat(db, oldClients[index].Email)
|
||||||
@@ -366,11 +411,16 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
db := database.GetDB()
|
|
||||||
dbInbound := db.Model(model.Inbound{})
|
|
||||||
|
|
||||||
|
traffics, err = s.adjustTraffics(traffics)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
db = db.Model(xray.ClientTraffic{})
|
db = db.Model(xray.ClientTraffic{})
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
@@ -378,7 +428,20 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
tx.Commit()
|
tx.Commit()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
err = tx.Save(traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("AddClientTraffic update data ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_traffics []*xray.ClientTraffic, err error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
dbInbound := db.Model(model.Inbound{})
|
||||||
txInbound := dbInbound.Begin()
|
txInbound := dbInbound.Begin()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
txInbound.Rollback()
|
txInbound.Rollback()
|
||||||
@@ -389,48 +452,66 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
|
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
client := &xray.ClientTraffic{}
|
client_traffic := &xray.ClientTraffic{}
|
||||||
err := tx.Where("email = ?", traffic.Email).First(client).Error
|
err := db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(client_traffic).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
logger.Warning(err, traffic.Email)
|
logger.Warning(err, traffic.Email)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
client_traffic.Up += traffic.Up
|
||||||
|
client_traffic.Down += traffic.Down
|
||||||
|
|
||||||
err = txInbound.Where("id=?", client.InboundId).First(inbound).Error
|
err = txInbound.Where("id=?", client_traffic.InboundId).First(inbound).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
logger.Warning(err, traffic.Email)
|
logger.Warning(err, traffic.Email)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// get settings clients
|
// get clients
|
||||||
settings := map[string][]model.Client{}
|
clients, err := s.getClients(inbound)
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
needUpdate := false
|
||||||
clients := settings["clients"]
|
if err == nil {
|
||||||
for _, client := range clients {
|
for client_index, client := range clients {
|
||||||
if traffic.Email == client.Email {
|
if traffic.Email == client.Email {
|
||||||
traffic.ExpiryTime = client.ExpiryTime
|
if client.ExpiryTime < 0 {
|
||||||
traffic.Total = client.TotalGB
|
clients[client_index].ExpiryTime = (time.Now().Unix() * 1000) - client.ExpiryTime
|
||||||
|
needUpdate = true
|
||||||
|
}
|
||||||
|
client_traffic.ExpiryTime = client.ExpiryTime
|
||||||
|
client_traffic.Total = client.TotalGB
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tx.Where("inbound_id = ? and email = ?", inbound.Id, traffic.Email).
|
|
||||||
UpdateColumns(map[string]interface{}{
|
if needUpdate {
|
||||||
"enable": true,
|
settings := map[string]interface{}{}
|
||||||
"expiry_time": traffic.ExpiryTime,
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
"total": traffic.Total,
|
|
||||||
"up": gorm.Expr("up + ?", traffic.Up),
|
// Convert clients to []interface to update clients in settings
|
||||||
"down": gorm.Expr("down + ?", traffic.Down)}).RowsAffected == 0 {
|
var clientsInterface []interface{}
|
||||||
err = tx.Create(traffic).Error
|
for _, c := range clients {
|
||||||
|
clientsInterface = append(clientsInterface, interface{}(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["clients"] = clientsInterface
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = txInbound.Where("id=?", inbound.Id).Update("settings", string(modifiedSettings)).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
full_traffics = append(full_traffics, client_traffic)
|
||||||
logger.Warning("AddClientTraffic update data ", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return full_traffics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||||
@@ -453,6 +534,17 @@ func (s *InboundService) DisableInvalidClients() (int64, error) {
|
|||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
func (s *InboundService) RemoveOrphanedTraffics() {
|
||||||
|
db := database.GetDB()
|
||||||
|
db.Exec(`
|
||||||
|
DELETE FROM client_traffics
|
||||||
|
WHERE email NOT IN (
|
||||||
|
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||||
|
FROM inbounds,
|
||||||
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
}
|
||||||
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -505,11 +597,58 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (s *InboundService) GetClientTrafficTgBot(tguname string) (traffic []*xray.ClientTraffic, err error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
var traffics []*xray.ClientTraffic
|
|
||||||
|
|
||||||
err = db.Model(xray.ClientTraffic{}).Where("email like ?", "%@"+tguname).Find(&traffics).Error
|
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
|
Where("inbound_id = ?", id).
|
||||||
|
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
|
err := result.Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ResetAllTraffics() error {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
result := db.Model(model.Inbound{}).
|
||||||
|
Where("user_id > ?", 0).
|
||||||
|
Updates(map[string]interface{}{"up": 0, "down": 0})
|
||||||
|
|
||||||
|
err := result.Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
|
||||||
|
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
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
@@ -571,3 +710,13 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client
|
|||||||
}
|
}
|
||||||
return traffic, err
|
return traffic, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("remark like ?", "%"+query+"%").Find(&inbounds).Error
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inbounds, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/sys"
|
"x-ui/util/sys"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
@@ -191,33 +194,35 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
versions := make([]string, 0, len(releases))
|
var versions []string
|
||||||
for _, release := range releases {
|
for _, release := range releases {
|
||||||
versions = append(versions, release.TagName)
|
if release.TagName >= "v1.8.0" {
|
||||||
|
versions = append(versions, release.TagName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return versions, nil
|
return versions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) StopXrayService() (string error) {
|
func (s *ServerService) StopXrayService() (string error) {
|
||||||
|
|
||||||
err := s.xrayService.StopXray()
|
err := s.xrayService.StopXray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("stop xray failed:", err)
|
logger.Error("stop xray failed:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) RestartXrayService() (string error) {
|
func (s *ServerService) RestartXrayService() (string error) {
|
||||||
|
|
||||||
s.xrayService.StopXray()
|
s.xrayService.StopXray()
|
||||||
defer func() {
|
defer func() {
|
||||||
err := s.xrayService.RestartXray(true)
|
err := s.xrayService.RestartXray(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("start xray failed:", err)
|
logger.Error("start xray failed:", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -324,3 +329,92 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetLogs(count string) ([]string, error) {
|
||||||
|
// Define the journalctl command and its arguments
|
||||||
|
var cmdArgs []string
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count}
|
||||||
|
} else {
|
||||||
|
return []string{"Unsupported operating system"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
|
||||||
|
return lines, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||||
|
// Open the file for reading
|
||||||
|
file, err := os.Open(xray.GetConfigPath())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Read the file contents
|
||||||
|
fileContents, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonData interface{}
|
||||||
|
err = json.Unmarshal(fileContents, &jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetDb() ([]byte, error) {
|
||||||
|
// Open the file for reading
|
||||||
|
file, err := os.Open(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Read the file contents
|
||||||
|
fileContents, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileContents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
|
||||||
|
privateKeyLine := strings.Split(lines[0], ":")
|
||||||
|
publicKeyLine := strings.Split(lines[1], ":")
|
||||||
|
|
||||||
|
privateKey := strings.TrimSpace(privateKeyLine[1])
|
||||||
|
publicKey := strings.TrimSpace(publicKeyLine[1])
|
||||||
|
|
||||||
|
keyPair := map[string]interface{}{
|
||||||
|
"privateKey": privateKey,
|
||||||
|
"publicKey": publicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ var defaultValueMap = map[string]string{
|
|||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
"webBasePath": "/",
|
"webBasePath": "/",
|
||||||
|
"expireDiff": "0",
|
||||||
|
"trafficDiff": "0",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Asia/Tehran",
|
||||||
"tgBotEnable": "false",
|
"tgBotEnable": "false",
|
||||||
"tgBotToken": "",
|
"tgBotToken": "",
|
||||||
"tgBotChatId": "",
|
"tgBotChatId": "",
|
||||||
"tgRunTime": "@daily",
|
"tgRunTime": "@daily",
|
||||||
"tgBotBackup": "false",
|
"tgBotBackup": "false",
|
||||||
"tgExpireDiff": "0",
|
|
||||||
"tgTrafficDiff": "0",
|
|
||||||
"tgCpu": "0",
|
"tgCpu": "0",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,22 +238,6 @@ func (s *SettingService) SetTgBotBackup(value bool) error {
|
|||||||
return s.setBool("tgBotBackup", value)
|
return s.setBool("tgBotBackup", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTgExpireDiff() (int, error) {
|
|
||||||
return s.getInt("tgExpireDiff")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) SetTgExpireDiff(value int) error {
|
|
||||||
return s.setInt("tgExpireDiff", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetTgTrafficDiff() (int, error) {
|
|
||||||
return s.getInt("tgTrafficDiff")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) SetTgTrafficDiff(value int) error {
|
|
||||||
return s.setInt("tgTrafficDiff", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetTgCpu() (int, error) {
|
func (s *SettingService) GetTgCpu() (int, error) {
|
||||||
return s.getInt("tgCpu")
|
return s.getInt("tgCpu")
|
||||||
}
|
}
|
||||||
@@ -278,6 +262,22 @@ func (s *SettingService) GetKeyFile() (string, error) {
|
|||||||
return s.getString("webKeyFile")
|
return s.getString("webKeyFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetExpireDiff() (int, error) {
|
||||||
|
return s.getInt("expireDiff")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetExpireDiff(value int) error {
|
||||||
|
return s.setInt("expireDiff", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTrafficDiff() (int, error) {
|
||||||
|
return s.getInt("trafficDiff")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetgetTrafficDiff(value int) error {
|
||||||
|
return s.setInt("trafficDiff", value)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||||
secret, err := s.getString("secret")
|
secret, err := s.getString("secret")
|
||||||
if secret == defaultValueMap["secret"] {
|
if secret == defaultValueMap["secret"] {
|
||||||
|
|||||||
505
web/service/sub.go
Normal file
505
web/service/sub.go
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/logger"
|
||||||
|
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubService struct {
|
||||||
|
address string
|
||||||
|
inboundService InboundService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
|
||||||
|
s.address = host
|
||||||
|
var result []string
|
||||||
|
inbounds, err := s.getInboundsBySubId(subId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
clients, err := s.inboundService.getClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
||||||
|
}
|
||||||
|
if clients == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, client := range clients {
|
||||||
|
if client.SubID == subId {
|
||||||
|
link := s.getLink(inbound, client.Email)
|
||||||
|
result = append(result, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inbounds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
|
switch inbound.Protocol {
|
||||||
|
case "vmess":
|
||||||
|
return s.genVmessLink(inbound, email)
|
||||||
|
case "vless":
|
||||||
|
return s.genVlessLink(inbound, email)
|
||||||
|
case "trojan":
|
||||||
|
return s.genTrojanLink(inbound, email)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
|
address := s.address
|
||||||
|
if inbound.Protocol != model.VMess {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
network, _ := stream["network"].(string)
|
||||||
|
typeStr := "none"
|
||||||
|
host := ""
|
||||||
|
path := ""
|
||||||
|
sni := ""
|
||||||
|
fp := ""
|
||||||
|
var alpn []string
|
||||||
|
allowInsecure := false
|
||||||
|
switch network {
|
||||||
|
case "tcp":
|
||||||
|
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := tcp["header"].(map[string]interface{})
|
||||||
|
typeStr, _ = header["type"].(string)
|
||||||
|
if typeStr == "http" {
|
||||||
|
request := header["request"].(map[string]interface{})
|
||||||
|
requestPath, _ := request["path"].([]interface{})
|
||||||
|
path = requestPath[0].(string)
|
||||||
|
headers, _ := request["headers"].(map[string]interface{})
|
||||||
|
host = searchHost(headers)
|
||||||
|
}
|
||||||
|
case "kcp":
|
||||||
|
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := kcp["header"].(map[string]interface{})
|
||||||
|
typeStr, _ = header["type"].(string)
|
||||||
|
path, _ = kcp["seed"].(string)
|
||||||
|
case "ws":
|
||||||
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
|
path = ws["path"].(string)
|
||||||
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
|
host = searchHost(headers)
|
||||||
|
case "http":
|
||||||
|
network = "h2"
|
||||||
|
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||||
|
path, _ = http["path"].(string)
|
||||||
|
host = searchHost(http)
|
||||||
|
case "quic":
|
||||||
|
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||||
|
header := quic["header"].(map[string]interface{})
|
||||||
|
typeStr, _ = header["type"].(string)
|
||||||
|
host, _ = quic["security"].(string)
|
||||||
|
path, _ = quic["key"].(string)
|
||||||
|
case "grpc":
|
||||||
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
|
path = grpc["serviceName"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
security, _ := stream["security"].(string)
|
||||||
|
if security == "tls" {
|
||||||
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
|
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||||
|
for _, a := range alpns {
|
||||||
|
alpn = append(alpn, a.(string))
|
||||||
|
}
|
||||||
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
|
if tlsSetting != nil {
|
||||||
|
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
||||||
|
sni, _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
|
fp, _ = fpValue.(string)
|
||||||
|
}
|
||||||
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
|
allowInsecure, _ = insecure.(bool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
|
if serverName != "" {
|
||||||
|
address = serverName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, _ := s.inboundService.getClients(inbound)
|
||||||
|
clientIndex := -1
|
||||||
|
for i, client := range clients {
|
||||||
|
if client.Email == email {
|
||||||
|
clientIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := map[string]interface{}{
|
||||||
|
"v": "2",
|
||||||
|
"ps": email,
|
||||||
|
"add": address,
|
||||||
|
"port": inbound.Port,
|
||||||
|
"id": clients[clientIndex].ID,
|
||||||
|
"aid": clients[clientIndex].AlterIds,
|
||||||
|
"net": network,
|
||||||
|
"type": typeStr,
|
||||||
|
"host": host,
|
||||||
|
"path": path,
|
||||||
|
"tls": security,
|
||||||
|
"sni": sni,
|
||||||
|
"fp": fp,
|
||||||
|
"alpn": strings.Join(alpn, ","),
|
||||||
|
"allowInsecure": allowInsecure,
|
||||||
|
}
|
||||||
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
|
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
|
address := s.address
|
||||||
|
if inbound.Protocol != model.VLESS {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
clients, _ := s.inboundService.getClients(inbound)
|
||||||
|
clientIndex := -1
|
||||||
|
for i, client := range clients {
|
||||||
|
if client.Email == email {
|
||||||
|
clientIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uuid := clients[clientIndex].ID
|
||||||
|
port := inbound.Port
|
||||||
|
streamNetwork := stream["network"].(string)
|
||||||
|
params := make(map[string]string)
|
||||||
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
|
switch streamNetwork {
|
||||||
|
case "tcp":
|
||||||
|
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := tcp["header"].(map[string]interface{})
|
||||||
|
typeStr, _ := header["type"].(string)
|
||||||
|
if typeStr == "http" {
|
||||||
|
request := header["request"].(map[string]interface{})
|
||||||
|
requestPath, _ := request["path"].([]interface{})
|
||||||
|
params["path"] = requestPath[0].(string)
|
||||||
|
headers, _ := request["headers"].(map[string]interface{})
|
||||||
|
params["host"] = searchHost(headers)
|
||||||
|
params["headerType"] = "http"
|
||||||
|
}
|
||||||
|
case "kcp":
|
||||||
|
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := kcp["header"].(map[string]interface{})
|
||||||
|
params["headerType"] = header["type"].(string)
|
||||||
|
params["seed"] = kcp["seed"].(string)
|
||||||
|
case "ws":
|
||||||
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
|
params["path"] = ws["path"].(string)
|
||||||
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
|
params["host"] = searchHost(headers)
|
||||||
|
case "http":
|
||||||
|
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||||
|
params["path"] = http["path"].(string)
|
||||||
|
params["host"] = searchHost(http)
|
||||||
|
case "quic":
|
||||||
|
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||||
|
params["quicSecurity"] = quic["security"].(string)
|
||||||
|
params["key"] = quic["key"].(string)
|
||||||
|
header := quic["header"].(map[string]interface{})
|
||||||
|
params["headerType"] = header["type"].(string)
|
||||||
|
case "grpc":
|
||||||
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ",")
|
||||||
|
}
|
||||||
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
|
if tlsSetting != nil {
|
||||||
|
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
|
params["fp"], _ = fpValue.(string)
|
||||||
|
}
|
||||||
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
|
if insecure.(bool) {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
|
params["flow"] = clients[clientIndex].Flow
|
||||||
|
}
|
||||||
|
|
||||||
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
|
if serverName != "" {
|
||||||
|
address = serverName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if security == "xtls" {
|
||||||
|
params["security"] = "xtls"
|
||||||
|
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||||
|
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||||
|
var alpn []string
|
||||||
|
for _, a := range alpns {
|
||||||
|
alpn = append(alpn, a.(string))
|
||||||
|
}
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||||
|
if xtlsSetting != nil {
|
||||||
|
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||||
|
params["fp"], _ = fpValue.(string)
|
||||||
|
}
|
||||||
|
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||||
|
if insecure.(bool) {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
|
params["flow"] = clients[clientIndex].Flow
|
||||||
|
}
|
||||||
|
|
||||||
|
serverName, _ := xtlsSetting["serverName"].(string)
|
||||||
|
if serverName != "" {
|
||||||
|
address = serverName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
url.Fragment = email
|
||||||
|
return url.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
|
||||||
|
address := s.address
|
||||||
|
if inbound.Protocol != model.Trojan {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
clients, _ := s.inboundService.getClients(inbound)
|
||||||
|
clientIndex := -1
|
||||||
|
for i, client := range clients {
|
||||||
|
if client.Email == email {
|
||||||
|
clientIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
password := clients[clientIndex].Password
|
||||||
|
port := inbound.Port
|
||||||
|
streamNetwork := stream["network"].(string)
|
||||||
|
params := make(map[string]string)
|
||||||
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
|
switch streamNetwork {
|
||||||
|
case "tcp":
|
||||||
|
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := tcp["header"].(map[string]interface{})
|
||||||
|
typeStr, _ := header["type"].(string)
|
||||||
|
if typeStr == "http" {
|
||||||
|
request := header["request"].(map[string]interface{})
|
||||||
|
requestPath, _ := request["path"].([]interface{})
|
||||||
|
params["path"] = requestPath[0].(string)
|
||||||
|
headers, _ := request["headers"].(map[string]interface{})
|
||||||
|
params["host"] = searchHost(headers)
|
||||||
|
params["headerType"] = "http"
|
||||||
|
}
|
||||||
|
case "kcp":
|
||||||
|
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := kcp["header"].(map[string]interface{})
|
||||||
|
params["headerType"] = header["type"].(string)
|
||||||
|
params["seed"] = kcp["seed"].(string)
|
||||||
|
case "ws":
|
||||||
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
|
params["path"] = ws["path"].(string)
|
||||||
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
|
params["host"] = searchHost(headers)
|
||||||
|
case "http":
|
||||||
|
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||||
|
params["path"] = http["path"].(string)
|
||||||
|
params["host"] = searchHost(http)
|
||||||
|
case "quic":
|
||||||
|
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||||
|
params["quicSecurity"] = quic["security"].(string)
|
||||||
|
params["key"] = quic["key"].(string)
|
||||||
|
header := quic["header"].(map[string]interface{})
|
||||||
|
params["headerType"] = header["type"].(string)
|
||||||
|
case "grpc":
|
||||||
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ",")
|
||||||
|
}
|
||||||
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
|
if tlsSetting != nil {
|
||||||
|
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
|
params["fp"], _ = fpValue.(string)
|
||||||
|
}
|
||||||
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
|
if insecure.(bool) {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
|
if serverName != "" {
|
||||||
|
address = serverName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if security == "xtls" {
|
||||||
|
params["security"] = "xtls"
|
||||||
|
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||||
|
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||||
|
var alpn []string
|
||||||
|
for _, a := range alpns {
|
||||||
|
alpn = append(alpn, a.(string))
|
||||||
|
}
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||||
|
if xtlsSetting != nil {
|
||||||
|
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||||
|
params["fp"], _ = fpValue.(string)
|
||||||
|
}
|
||||||
|
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||||
|
if insecure.(bool) {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
|
params["flow"] = clients[clientIndex].Flow
|
||||||
|
}
|
||||||
|
|
||||||
|
serverName, _ := xtlsSetting["serverName"].(string)
|
||||||
|
if serverName != "" {
|
||||||
|
address = serverName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
||||||
|
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
url.Fragment = email
|
||||||
|
return url.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
|
switch val := data.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
for k, v := range val {
|
||||||
|
if k == key {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
if result, ok := searchKey(v, key); ok {
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
for _, v := range val {
|
||||||
|
if result, ok := searchKey(v, key); ok {
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchHost(headers interface{}) string {
|
||||||
|
data, _ := headers.(map[string]interface{})
|
||||||
|
for k, v := range data {
|
||||||
|
if strings.EqualFold(k, "host") {
|
||||||
|
switch v.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
hosts, _ := v.([]interface{})
|
||||||
|
return hosts[0].(string)
|
||||||
|
case interface{}:
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -105,8 +105,6 @@ func (t *Tgbot) OnReceive() {
|
|||||||
} else {
|
} else {
|
||||||
if update.Message.IsCommand() {
|
if update.Message.IsCommand() {
|
||||||
t.answerCommand(update.Message, chatId, isAdmin)
|
t.answerCommand(update.Message, chatId, isAdmin)
|
||||||
} else {
|
|
||||||
t.aswerChat(update.Message.Text, chatId, isAdmin)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,10 +126,20 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
|
|||||||
case "status":
|
case "status":
|
||||||
msg = "bot is ok ✅"
|
msg = "bot is ok ✅"
|
||||||
case "usage":
|
case "usage":
|
||||||
if isAdmin {
|
if len(message.CommandArguments()) > 1 {
|
||||||
t.searchClient(chatId, message.CommandArguments())
|
if isAdmin {
|
||||||
|
t.searchClient(chatId, message.CommandArguments())
|
||||||
|
} else {
|
||||||
|
t.searchForClient(chatId, message.CommandArguments())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
t.searchForClient(chatId, message.CommandArguments())
|
msg = "❗Please provide a text for search!"
|
||||||
|
}
|
||||||
|
case "inbound":
|
||||||
|
if isAdmin {
|
||||||
|
t.searchInbound(chatId, message.CommandArguments())
|
||||||
|
} else {
|
||||||
|
msg = "❗ Unknown command"
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
msg = "❗ Unknown command"
|
msg = "❗ Unknown command"
|
||||||
@@ -139,10 +147,6 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
|
|||||||
t.SendAnswer(chatId, msg, isAdmin)
|
t.SendAnswer(chatId, msg, isAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) aswerChat(message string, chatId int64, isAdmin bool) {
|
|
||||||
t.SendAnswer(chatId, "❗ Unknown message", isAdmin)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
|
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
|
||||||
// Respond to the callback query, telling Telegram to show the user
|
// Respond to the callback query, telling Telegram to show the user
|
||||||
// a message with the data received.
|
// a message with the data received.
|
||||||
@@ -156,16 +160,16 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
|
|||||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.getServerUsage())
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.getServerUsage())
|
||||||
case "inbounds":
|
case "inbounds":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.getInboundUsages())
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.getInboundUsages())
|
||||||
case "exhausted_soon":
|
case "deplete_soon":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.getExhausted())
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.getExhausted())
|
||||||
case "get_backup":
|
case "get_backup":
|
||||||
t.sendBackup(callbackQuery.From.ID)
|
t.sendBackup(callbackQuery.From.ID)
|
||||||
case "client_traffic":
|
case "client_traffic":
|
||||||
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, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess and vless and Password for Trojan.")
|
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Password]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
|
||||||
case "commands":
|
case "commands":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for a client email, just use folowing command:\r\n \r\n<code>/usage email</code>")
|
t.SendMsgToTgbot(callbackQuery.From.ID, "Search for a client email:\r\n<code>/usage email</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [remark]</code>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +190,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
|||||||
),
|
),
|
||||||
tgbotapi.NewInlineKeyboardRow(
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
tgbotapi.NewInlineKeyboardButtonData("Get Inbounds", "inbounds"),
|
tgbotapi.NewInlineKeyboardButtonData("Get Inbounds", "inbounds"),
|
||||||
tgbotapi.NewInlineKeyboardButtonData("Exhausted soon", "exhausted_soon"),
|
tgbotapi.NewInlineKeyboardButtonData("Deplete soon", "deplete_soon"),
|
||||||
),
|
),
|
||||||
tgbotapi.NewInlineKeyboardRow(
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
tgbotapi.NewInlineKeyboardButtonData("Commands", "commands"),
|
tgbotapi.NewInlineKeyboardButtonData("Commands", "commands"),
|
||||||
@@ -272,6 +276,7 @@ func (t *Tgbot) getServerUsage() string {
|
|||||||
name = ""
|
name = ""
|
||||||
}
|
}
|
||||||
info = fmt.Sprintf("💻 Hostname: %s\r\n", name)
|
info = fmt.Sprintf("💻 Hostname: %s\r\n", name)
|
||||||
|
info += fmt.Sprintf("🚀X-UI Version: %s\r\n", config.GetVersion())
|
||||||
//get ip address
|
//get ip address
|
||||||
var ip string
|
var ip string
|
||||||
var ipv6 string
|
var ipv6 string
|
||||||
@@ -358,6 +363,11 @@ func (t *Tgbot) getInboundUsages() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
||||||
|
if len(tgUserName) == 0 {
|
||||||
|
msg := "Your configuration is not found!\nYou should configure your telegram username and ask Admin to add it to your configuration."
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
|
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
@@ -368,11 +378,14 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
|||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram username in your configuration(s).\n\nYour username: <b>@" + tgUserName + "</b>"
|
msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram username in your configuration(s).\n\nYour username: <b>@" + tgUserName + "</b>"
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = "♾Unlimited"
|
expiryTime = "♾Unlimited"
|
||||||
|
} else if traffic.ExpiryTime < 0 {
|
||||||
|
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||||
} else {
|
} else {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
@@ -407,6 +420,8 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
|
|||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = "♾Unlimited"
|
expiryTime = "♾Unlimited"
|
||||||
|
} else if traffic.ExpiryTime < 0 {
|
||||||
|
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||||
} else {
|
} else {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
@@ -423,6 +438,47 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
||||||
|
inbouds, err := t.inboundService.SearchInbounds(remark)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
msg := "❌ Something went wrong!"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, inbound := range inbouds {
|
||||||
|
info := ""
|
||||||
|
info += fmt.Sprintf("📍Inbound:%s\r\nPort:%d\r\n", inbound.Remark, inbound.Port)
|
||||||
|
info += fmt.Sprintf("Traffic: %s (↑%s,↓%s)\r\n", common.FormatTraffic((inbound.Up + inbound.Down)), common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down))
|
||||||
|
if inbound.ExpiryTime == 0 {
|
||||||
|
info += "Expire date: ♾ Unlimited\r\n \r\n"
|
||||||
|
} else {
|
||||||
|
info += fmt.Sprintf("Expire date:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
t.SendMsgToTgbot(chatId, info)
|
||||||
|
for _, traffic := range inbound.ClientStats {
|
||||||
|
expiryTime := ""
|
||||||
|
if traffic.ExpiryTime == 0 {
|
||||||
|
expiryTime = "♾Unlimited"
|
||||||
|
} else if traffic.ExpiryTime < 0 {
|
||||||
|
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||||
|
} else {
|
||||||
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
total := ""
|
||||||
|
if traffic.Total == 0 {
|
||||||
|
total = "♾Unlimited"
|
||||||
|
} else {
|
||||||
|
total = common.FormatTraffic((traffic.Total))
|
||||||
|
}
|
||||||
|
output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
|
||||||
|
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
||||||
|
total, expiryTime)
|
||||||
|
t.SendMsgToTgbot(chatId, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tgbot) searchForClient(chatId int64, query string) {
|
func (t *Tgbot) searchForClient(chatId int64, query string) {
|
||||||
traffic, err := t.inboundService.SearchClientTraffic(query)
|
traffic, err := t.inboundService.SearchClientTraffic(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -439,6 +495,8 @@ func (t *Tgbot) searchForClient(chatId int64, query string) {
|
|||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = "♾Unlimited"
|
expiryTime = "♾Unlimited"
|
||||||
|
} else if traffic.ExpiryTime < 0 {
|
||||||
|
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||||
} else {
|
} else {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
@@ -463,13 +521,13 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
var disabledInbounds []model.Inbound
|
var disabledInbounds []model.Inbound
|
||||||
var disabledClients []xray.ClientTraffic
|
var disabledClients []xray.ClientTraffic
|
||||||
output := ""
|
output := ""
|
||||||
TrafficThreshold, err := t.settingService.GetTgTrafficDiff()
|
TrafficThreshold, err := t.settingService.GetTrafficDiff()
|
||||||
if err == nil && TrafficThreshold > 0 {
|
if err == nil && TrafficThreshold > 0 {
|
||||||
trDiff = int64(TrafficThreshold) * 1073741824
|
trDiff = int64(TrafficThreshold) * 1073741824
|
||||||
}
|
}
|
||||||
ExpireThreshold, err := t.settingService.GetTgExpireDiff()
|
ExpireThreshold, err := t.settingService.GetExpireDiff()
|
||||||
if err == nil && ExpireThreshold > 0 {
|
if err == nil && ExpireThreshold > 0 {
|
||||||
exDiff = int64(ExpireThreshold) * 84600
|
exDiff = int64(ExpireThreshold) * 86400000
|
||||||
}
|
}
|
||||||
inbounds, err := t.inboundService.GetAllInbounds()
|
inbounds, err := t.inboundService.GetAllInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -477,15 +535,15 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
}
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
if inbound.Enable {
|
if inbound.Enable {
|
||||||
if (inbound.ExpiryTime > 0 && (now-inbound.ExpiryTime < exDiff)) ||
|
if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) ||
|
||||||
(inbound.Total > 0 && (inbound.Total-inbound.Up+inbound.Down < trDiff)) {
|
(inbound.Total > 0 && (inbound.Total-(inbound.Up+inbound.Down) < trDiff)) {
|
||||||
exhaustedInbounds = append(exhaustedInbounds, *inbound)
|
exhaustedInbounds = append(exhaustedInbounds, *inbound)
|
||||||
}
|
}
|
||||||
if len(inbound.ClientStats) > 0 {
|
if len(inbound.ClientStats) > 0 {
|
||||||
for _, client := range inbound.ClientStats {
|
for _, client := range inbound.ClientStats {
|
||||||
if client.Enable {
|
if client.Enable {
|
||||||
if (client.ExpiryTime > 0 && (now-client.ExpiryTime < exDiff)) ||
|
if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) ||
|
||||||
(client.Total > 0 && (client.Total-client.Up+client.Down < trDiff)) {
|
(client.Total > 0 && (client.Total-(client.Up+client.Down) < trDiff)) {
|
||||||
exhaustedClients = append(exhaustedClients, client)
|
exhaustedClients = append(exhaustedClients, client)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -497,8 +555,8 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
disabledInbounds = append(disabledInbounds, *inbound)
|
disabledInbounds = append(disabledInbounds, *inbound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output += fmt.Sprintf("Exhausted Inbounds count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledInbounds), len(exhaustedInbounds))
|
output += fmt.Sprintf("Exhausted Inbounds count:\r\n🛑 Disabled: %d\r\n🔜 Deplete soon: %d\r\n \r\n", len(disabledInbounds), len(exhaustedInbounds))
|
||||||
if len(disabledInbounds)+len(exhaustedInbounds) > 0 {
|
if len(exhaustedInbounds) > 0 {
|
||||||
output += "Exhausted Inbounds:\r\n"
|
output += "Exhausted Inbounds:\r\n"
|
||||||
for _, inbound := range exhaustedInbounds {
|
for _, inbound := range exhaustedInbounds {
|
||||||
output += fmt.Sprintf("📍Inbound:%s\r\nPort:%d\r\nTraffic: %s (↑%s,↓%s)\r\n", inbound.Remark, inbound.Port, common.FormatTraffic((inbound.Up + inbound.Down)), common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down))
|
output += fmt.Sprintf("📍Inbound:%s\r\nPort:%d\r\nTraffic: %s (↑%s,↓%s)\r\n", inbound.Remark, inbound.Port, common.FormatTraffic((inbound.Up + inbound.Down)), common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down))
|
||||||
@@ -509,13 +567,15 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output += fmt.Sprintf("Exhausted Clients count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledClients), len(exhaustedClients))
|
output += fmt.Sprintf("Exhausted Clients count:\r\n🛑 Exhausted: %d\r\n🔜 Deplete soon: %d\r\n \r\n", len(disabledClients), len(exhaustedClients))
|
||||||
if len(disabledClients)+len(exhaustedClients) > 0 {
|
if len(exhaustedClients) > 0 {
|
||||||
output += "Exhausted Clients:\r\n"
|
output += "Exhausted Clients:\r\n"
|
||||||
for _, traffic := range exhaustedClients {
|
for _, traffic := range exhaustedClients {
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = "♾Unlimited"
|
expiryTime = "♾Unlimited"
|
||||||
|
} else if traffic.ExpiryTime < 0 {
|
||||||
|
expiryTime += fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||||
} else {
|
} else {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
@@ -525,7 +585,7 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
} else {
|
} else {
|
||||||
total = common.FormatTraffic((traffic.Total))
|
total = common.FormatTraffic((traffic.Total))
|
||||||
}
|
}
|
||||||
output += fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
|
output += fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire date: %s\r\n \r\n",
|
||||||
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
||||||
total, expiryTime)
|
total, expiryTime)
|
||||||
}
|
}
|
||||||
@@ -543,4 +603,10 @@ func (t *Tgbot) sendBackup(chatId int64) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Error in uploading backup: ", err)
|
logger.Warning("Error in uploading backup: ", err)
|
||||||
}
|
}
|
||||||
|
file = tgbotapi.FilePath(xray.GetConfigPath())
|
||||||
|
msg = tgbotapi.NewDocument(chatId, file)
|
||||||
|
_, err = bot.Send(msg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Error in uploading config.json: ", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.inboundService.DisableInvalidClients()
|
s.inboundService.DisableInvalidClients()
|
||||||
|
s.inboundService.RemoveOrphanedTraffics()
|
||||||
|
|
||||||
inbounds, err := s.inboundService.GetAllInbounds()
|
inbounds, err := s.inboundService.GetAllInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -84,15 +85,16 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
clients, ok := settings["clients"].([]interface{})
|
clients, ok := settings["clients"].([]interface{})
|
||||||
if ok {
|
if ok {
|
||||||
// check users active or not
|
// check users active or not
|
||||||
|
|
||||||
clientStats := inbound.ClientStats
|
clientStats := inbound.ClientStats
|
||||||
for _, clientTraffic := range clientStats {
|
for _, clientTraffic := range clientStats {
|
||||||
|
|
||||||
|
indexDecrease := 0
|
||||||
for index, client := range clients {
|
for index, client := range clients {
|
||||||
c := client.(map[string]interface{})
|
c := client.(map[string]interface{})
|
||||||
if c["email"] == clientTraffic.Email {
|
if c["email"] == clientTraffic.Email {
|
||||||
if !clientTraffic.Enable {
|
if !clientTraffic.Enable {
|
||||||
clients = RemoveIndex(clients, index)
|
clients = RemoveIndex(clients, index-indexDecrease)
|
||||||
|
indexDecrease++
|
||||||
logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit")
|
logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit")
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -101,7 +103,27 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
settings["clients"] = clients
|
|
||||||
|
// clear client config for additional parameters
|
||||||
|
var final_clients []interface{}
|
||||||
|
for _, client := range clients {
|
||||||
|
|
||||||
|
c := client.(map[string]interface{})
|
||||||
|
|
||||||
|
if c["enable"] != nil {
|
||||||
|
if enable, ok := c["enable"].(bool); ok && !enable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key := range c {
|
||||||
|
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
|
||||||
|
delete(c, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final_clients = append(final_clients, interface{}(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["clients"] = final_clients
|
||||||
modifiedSettings, err := json.Marshal(settings)
|
modifiedSettings, err := json.Marshal(settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -33,10 +33,13 @@
|
|||||||
"host" = "Host"
|
"host" = "Host"
|
||||||
"path" = "Path"
|
"path" = "Path"
|
||||||
"camouflage" = "Camouflage"
|
"camouflage" = "Camouflage"
|
||||||
|
"status" = "Status"
|
||||||
"enabled" = "Enabled"
|
"enabled" = "Enabled"
|
||||||
"disabled" = "Disabled"
|
"disabled" = "Disabled"
|
||||||
|
"depleted" = "Depleted"
|
||||||
|
"depletingSoon" = "Depleting soon"
|
||||||
"domainName" = "Domain name"
|
"domainName" = "Domain name"
|
||||||
"additional" = "Alter"
|
"additional" = "Alter ID"
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
"certificate" = "Certificat"
|
"certificate" = "Certificat"
|
||||||
"fail" = "Fail"
|
"fail" = "Fail"
|
||||||
@@ -69,8 +72,8 @@
|
|||||||
"memory" = "Memory"
|
"memory" = "Memory"
|
||||||
"hard" = "Hard disk"
|
"hard" = "Hard disk"
|
||||||
"xrayStatus" = "xray Status"
|
"xrayStatus" = "xray Status"
|
||||||
"stopXray" = "Stop xray"
|
"stopXray" = "Stop"
|
||||||
"restartXray" = "Restart xray"
|
"restartXray" = "Restart"
|
||||||
"xraySwitch" = "Switch Version"
|
"xraySwitch" = "Switch Version"
|
||||||
"xraySwitchClick" = "Click on the version you want to switch"
|
"xraySwitchClick" = "Click on the version you want to switch"
|
||||||
"xraySwitchClickDesk" = "Please choose carefully, older versions may have incompatible configurations"
|
"xraySwitchClickDesk" = "Please choose carefully, older versions may have incompatible configurations"
|
||||||
@@ -103,8 +106,8 @@
|
|||||||
"expireDate" = "Expire date"
|
"expireDate" = "Expire date"
|
||||||
"resetTraffic" = "Reset traffic"
|
"resetTraffic" = "Reset traffic"
|
||||||
"addInbound" = "Add Inbound"
|
"addInbound" = "Add Inbound"
|
||||||
"addTo" = "Add To"
|
"addTo" = "Create"
|
||||||
"revise" = "Revise"
|
"revise" = "Update"
|
||||||
"modifyInbound" = "Modify InBound"
|
"modifyInbound" = "Modify InBound"
|
||||||
"deleteInbound" = "Delete Inbound"
|
"deleteInbound" = "Delete Inbound"
|
||||||
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
||||||
@@ -128,7 +131,19 @@
|
|||||||
"keyContent" = "Key content"
|
"keyContent" = "Key content"
|
||||||
"clickOnQRcode" = "Click on QR Code to Copy"
|
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||||
"client" = "Client"
|
"client" = "Client"
|
||||||
"export" = "Export links"
|
"export" = "Export Links"
|
||||||
|
"Clone" = "Clone"
|
||||||
|
"cloneInbound" = "Create"
|
||||||
|
"cloneInboundContent" = "All items of this inbound except Port, Listening IP, Clients will be applied to the clone"
|
||||||
|
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
||||||
|
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
||||||
|
"resetAllTrafficContent" = "Are you sure to reset all inbounds traffic ?"
|
||||||
|
"resetAllClientTraffics" = "Reset Clients Traffic"
|
||||||
|
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
||||||
|
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
||||||
|
"Email" = "Email"
|
||||||
|
"EmailDesc" = "The Email Must Be Completely Unique"
|
||||||
|
"setDefaultCert" = "Set cert from panel"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Add client"
|
"add" = "Add client"
|
||||||
@@ -141,7 +156,10 @@
|
|||||||
"first" = "First"
|
"first" = "First"
|
||||||
"last" = "Last"
|
"last" = "Last"
|
||||||
"prefix" = "Prefix"
|
"prefix" = "Prefix"
|
||||||
"postfix" = "postfix"
|
"postfix" = "Postfix"
|
||||||
|
"delayedStart" = "Start after first use"
|
||||||
|
"expireDays" = "Expire days"
|
||||||
|
"days" = "day(s)"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Obtain"
|
"obtain" = "Obtain"
|
||||||
@@ -179,7 +197,7 @@
|
|||||||
"panelPortDesc" = "Restart the panel to take effect"
|
"panelPortDesc" = "Restart the panel to take effect"
|
||||||
"publicKeyPath" = "Panel certificate public key file path"
|
"publicKeyPath" = "Panel certificate public key file path"
|
||||||
"publicKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"publicKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
||||||
"privateKeyPath" = "Panel certificate key file path"
|
"privateKeyPath" = "Panel certificate private key file path"
|
||||||
"privateKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"privateKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
||||||
"panelUrlPath" = "panel url root path"
|
"panelUrlPath" = "panel url root path"
|
||||||
"panelUrlPathDesc" = "Must start with '/' and end with '/', restart the panel to take effect"
|
"panelUrlPathDesc" = "Must start with '/' and end with '/', restart the panel to take effect"
|
||||||
@@ -211,10 +229,10 @@
|
|||||||
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
||||||
"tgNotifyBackup" = "Database backup"
|
"tgNotifyBackup" = "Database backup"
|
||||||
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
||||||
"tgNotifyExpireTimeDiff" = "Remained time threshold"
|
"expireTimeDiff" = "Exhaustion time threshold"
|
||||||
"tgNotifyExpireTimeDiffDesc" = "This telegram bot will send you a notification before expiration (unit:day)"
|
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
||||||
"tgNotifyTrafficDiff" = "Remained traffic threshold"
|
"trafficDiff" = "Exhaustion traffic threshold"
|
||||||
"tgNotifyTrafficDiffDesc" = "This telegram bot will send you a notification before finishing traffic (unit:GB)"
|
"trafficDiffDesc" = "Detect exhaustion before finishing traffic (unit:GB)"
|
||||||
"tgNotifyCpu" = "CPU percentage alert threshold"
|
"tgNotifyCpu" = "CPU percentage alert threshold"
|
||||||
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
|
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
|
||||||
"timeZonee" = "Time Zone"
|
"timeZonee" = "Time Zone"
|
||||||
|
|||||||
@@ -33,12 +33,15 @@
|
|||||||
"host" = "آدرس"
|
"host" = "آدرس"
|
||||||
"path" = "مسیر"
|
"path" = "مسیر"
|
||||||
"camouflage" = "استتار"
|
"camouflage" = "استتار"
|
||||||
|
"status" = "وضعیت"
|
||||||
"enabled" = "فعال"
|
"enabled" = "فعال"
|
||||||
"disabled" = "غیرفعال"
|
"disabled" = "غیرفعال"
|
||||||
|
"depleted" = "منقضی"
|
||||||
|
"depletingSoon" = "در حال انقضا"
|
||||||
"domainName" = "آدرس دامنه"
|
"domainName" = "آدرس دامنه"
|
||||||
"additional" = "آی دی جایگزین"
|
"additional" = "آی دی جایگزین"
|
||||||
"monitor" = "آی پی اتصال"
|
"monitor" = "آی پی اتصال"
|
||||||
"certificate" = "سرتیفیکیت"
|
"certificate" = "گواهی دیجیتال"
|
||||||
"fail" = "خطا"
|
"fail" = "خطا"
|
||||||
"success" = " موفق"
|
"success" = " موفق"
|
||||||
"getVersion" = "دریافت ورژن"
|
"getVersion" = "دریافت ورژن"
|
||||||
@@ -69,13 +72,13 @@
|
|||||||
"memory" = "حافظه رم"
|
"memory" = "حافظه رم"
|
||||||
"hard" = "حافظه دیسک"
|
"hard" = "حافظه دیسک"
|
||||||
"xrayStatus" = "وضعیت Xray"
|
"xrayStatus" = "وضعیت Xray"
|
||||||
"stopXray" = "توقف xray"
|
"stopXray" = "توقف"
|
||||||
"restartXray" = "شروع مجدد xray"
|
"restartXray" = "شروع مجدد"
|
||||||
"xraySwitch" = "تغییر ورژن"
|
"xraySwitch" = "تغییر ورژن"
|
||||||
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
||||||
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
||||||
"operationHours" = "ساعت فعال"
|
"operationHours" = "مدت فعالیت"
|
||||||
"operationHoursDesc" = "ساعت فعال بعد از شروع سیستم"
|
"operationHoursDesc" = "مدت فعالیت سیستم بعد از روشن شدن"
|
||||||
"systemLoad" = "بار روی سیستم"
|
"systemLoad" = "بار روی سیستم"
|
||||||
"connectionCount" = "تعداد کانکشن ها"
|
"connectionCount" = "تعداد کانکشن ها"
|
||||||
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"
|
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"
|
||||||
@@ -114,7 +117,7 @@
|
|||||||
"network" = "شبکه"
|
"network" = "شبکه"
|
||||||
"destinationPort" = "پورت مقصد"
|
"destinationPort" = "پورت مقصد"
|
||||||
"targetAddress" = "آدرس مقصد"
|
"targetAddress" = "آدرس مقصد"
|
||||||
"disableInsecureEncryption" = "رمزگذاری ناامن را غیرفعال کنید"
|
"disableInsecureEncryption" = "غیرفعال سازی رمزگذاری ناامن"
|
||||||
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
||||||
"meansNoLimit" = "یعنی بدون محدودیت"
|
"meansNoLimit" = "یعنی بدون محدودیت"
|
||||||
"totalFlow" = "کل ترافیک"
|
"totalFlow" = "کل ترافیک"
|
||||||
@@ -122,13 +125,25 @@
|
|||||||
"noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود"
|
"noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود"
|
||||||
"certificatePath" = "مسیر فایل گواهی"
|
"certificatePath" = "مسیر فایل گواهی"
|
||||||
"certificateContent" = "محتوای فایل گواهی"
|
"certificateContent" = "محتوای فایل گواهی"
|
||||||
"publicKeyPath" = "مسیر فایل Certificate.crt"
|
"publicKeyPath" = "مسیر کلید عمومی"
|
||||||
"publicKeyContent" = "محتوای Certificate.crt"
|
"publicKeyContent" = "محتوای کلید عمومی"
|
||||||
"keyPath" = "مسیر فایل Private.key"
|
"keyPath" = "مسیر کلید خصوصی"
|
||||||
"keyContent" = "محتوای Private.key"
|
"keyContent" = "محتوای کلید خصوصی"
|
||||||
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
||||||
"client" = "کاربر"
|
"client" = "کاربر"
|
||||||
"export" = "استخراج لینکها"
|
"export" = "استخراج لینکها"
|
||||||
|
"Clone" = "شبیه سازی"
|
||||||
|
"cloneInbound" = "ایجاد"
|
||||||
|
"cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد"
|
||||||
|
"resetAllTraffic" = "ریست ترافیک کل سرویس ها"
|
||||||
|
"resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها"
|
||||||
|
"resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟"
|
||||||
|
"resetAllClientTraffics" = "ریست ترافیک کاربران"
|
||||||
|
"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
||||||
|
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
|
||||||
|
"Email" = "ایمیل"
|
||||||
|
"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
||||||
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "کاربر جدید"
|
"add" = "کاربر جدید"
|
||||||
@@ -142,6 +157,9 @@
|
|||||||
"last" = "تا"
|
"last" = "تا"
|
||||||
"prefix" = "پیشوند"
|
"prefix" = "پیشوند"
|
||||||
"postfix" = "پسوند"
|
"postfix" = "پسوند"
|
||||||
|
"delayedStart" = "شروع بعد از اولین استفاده"
|
||||||
|
"expireDays" = "روزهای اعتبار"
|
||||||
|
"days" = "(روز)"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Obtain"
|
"obtain" = "Obtain"
|
||||||
@@ -177,9 +195,9 @@
|
|||||||
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"panelPort" = "پورت پنل"
|
"panelPort" = "پورت پنل"
|
||||||
"panelPortDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelPortDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"publicKeyPath" = "مسیر فایل پنل Certificate.crt"
|
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
|
||||||
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"privateKeyPath" = "مسیر فایل پنل private.key"
|
"privateKeyPath" = "مسیر فایل گواهی کلید خصوصی پنل"
|
||||||
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"panelUrlPath" = "آدرس روت پنل"
|
"panelUrlPath" = "آدرس روت پنل"
|
||||||
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
@@ -208,13 +226,13 @@
|
|||||||
"telegramChatId" = "آی دی تلگرام مدیریت"
|
"telegramChatId" = "آی دی تلگرام مدیریت"
|
||||||
"telegramChatIdDesc" = "با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramChatIdDesc" = "با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
||||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی Crontab استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||||
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||||
"tgNotifyExpireTimeDiff" = "آستانه زمان باقی مانده"
|
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
||||||
"tgNotifyExpireTimeDiffDesc" = "این ربات تلگرام قبل از انقضا برای شما پیام ارسال می کند (واحد: روز)"
|
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
||||||
"tgNotifyTrafficDiff" = "آستانه ترافیک باقی مانده"
|
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
||||||
"tgNotifyTrafficDiffDesc" = "این ربات تلگرام قبل از اتمام ترافیک برای شما پیام ارسال می کند (واحد: گیگابایت)"
|
"trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)"
|
||||||
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
|
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
|
||||||
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
||||||
"timeZonee" = "منظقه زمانی"
|
"timeZonee" = "منظقه زمانی"
|
||||||
|
|||||||
@@ -33,10 +33,13 @@
|
|||||||
"host" = "主持人"
|
"host" = "主持人"
|
||||||
"path" = "小路"
|
"path" = "小路"
|
||||||
"camouflage" = "伪装"
|
"camouflage" = "伪装"
|
||||||
|
"status" = "状态"
|
||||||
"enabled" = "开启"
|
"enabled" = "开启"
|
||||||
"disabled" = "关闭"
|
"disabled" = "关闭"
|
||||||
|
"depleted" = "耗尽"
|
||||||
|
"depletingSoon" = "即将耗尽"
|
||||||
"domainName" = "域名"
|
"domainName" = "域名"
|
||||||
"additional" = "额外"
|
"additional" = "额外 ID"
|
||||||
"monitor" = "监听"
|
"monitor" = "监听"
|
||||||
"certificate" = "证书"
|
"certificate" = "证书"
|
||||||
"fail" = "失败"
|
"fail" = "失败"
|
||||||
@@ -69,8 +72,8 @@
|
|||||||
"memory" = "内存"
|
"memory" = "内存"
|
||||||
"hard" = "硬盘"
|
"hard" = "硬盘"
|
||||||
"xrayStatus" = "xray 状态"
|
"xrayStatus" = "xray 状态"
|
||||||
"stopXray" = "停止 Xray"
|
"stopXray" = "停止"
|
||||||
"restartXray" = "重启 Xray"
|
"restartXray" = "重启"
|
||||||
"xraySwitch" = "切换版本"
|
"xraySwitch" = "切换版本"
|
||||||
"xraySwitchClick" = "点击你想切换的版本"
|
"xraySwitchClick" = "点击你想切换的版本"
|
||||||
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
|
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
|
||||||
@@ -129,6 +132,18 @@
|
|||||||
"clickOnQRcode" = "点击二维码复制"
|
"clickOnQRcode" = "点击二维码复制"
|
||||||
"client" = "客户"
|
"client" = "客户"
|
||||||
"export" = "导出链接"
|
"export" = "导出链接"
|
||||||
|
"Clone" = "克隆"
|
||||||
|
"cloneInbound" = "创造"
|
||||||
|
"cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆"
|
||||||
|
"resetAllTraffic" = "重置所有入站流量"
|
||||||
|
"resetAllTrafficTitle" = "重置所有入站流量"
|
||||||
|
"resetAllTrafficContent" = "您确定要重置所有入站流量吗?"
|
||||||
|
"resetAllClientTraffics" = "重置客户端流量"
|
||||||
|
"resetAllClientTrafficTitle" = "重置所有客户端流量"
|
||||||
|
"resetAllClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
|
||||||
|
"Email" = "电子邮件"
|
||||||
|
"EmailDesc" = "电子邮件必须完全唯"
|
||||||
|
"setDefaultCert" = "从面板设置证书"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "添加客户端"
|
"add" = "添加客户端"
|
||||||
@@ -142,6 +157,9 @@
|
|||||||
"last" = "最后"
|
"last" = "最后"
|
||||||
"prefix" = "前缀"
|
"prefix" = "前缀"
|
||||||
"postfix" = "后缀"
|
"postfix" = "后缀"
|
||||||
|
"delayedStart" = "首次使用后开始"
|
||||||
|
"expireDays" = "过期天数"
|
||||||
|
"days" = "天"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "获取"
|
"obtain" = "获取"
|
||||||
@@ -211,10 +229,10 @@
|
|||||||
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
||||||
"tgNotifyBackup" = "数据库备份"
|
"tgNotifyBackup" = "数据库备份"
|
||||||
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
||||||
"tgNotifyExpireTimeDiff" = "剩余时间阈值"
|
"expireTimeDiff" = "耗尽时间阈值"
|
||||||
"tgNotifyExpireTimeDiffDesc" = "这个 talegram bot 会在到期前给你发送通知(单位:天)"
|
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
||||||
"tgNotifyTrafficDiff" = "剩余流量阈值"
|
"trafficDiff" = "耗尽流量阈值"
|
||||||
"tgNotifyTrafficDiffDesc" = "这个 talegram bot 会在流量结束前给你发送通知(单位:GB)"
|
"trafficDiffDesc" = "完成流量前检测耗尽(单位:GB)"
|
||||||
"tgNotifyCpu" = "CPU 百分比警报阈值"
|
"tgNotifyCpu" = "CPU 百分比警报阈值"
|
||||||
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
||||||
"timeZonee" = "时区"
|
"timeZonee" = "时区"
|
||||||
|
|||||||
10
web/web.go
10
web/web.go
@@ -33,6 +33,9 @@ import (
|
|||||||
//go:embed assets/*
|
//go:embed assets/*
|
||||||
var assetsFS embed.FS
|
var assetsFS embed.FS
|
||||||
|
|
||||||
|
//go:embed assets/favicon.ico
|
||||||
|
var favicon []byte
|
||||||
|
|
||||||
//go:embed html/*
|
//go:embed html/*
|
||||||
var htmlFS embed.FS
|
var htmlFS embed.FS
|
||||||
|
|
||||||
@@ -85,6 +88,7 @@ type Server struct {
|
|||||||
server *controller.ServerController
|
server *controller.ServerController
|
||||||
xui *controller.XUIController
|
xui *controller.XUIController
|
||||||
api *controller.APIController
|
api *controller.APIController
|
||||||
|
sub *controller.SUBController
|
||||||
|
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
@@ -157,6 +161,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
|
// Add favicon
|
||||||
|
engine.GET("/favicon.ico", func(c *gin.Context) {
|
||||||
|
c.Data(200, "image/x-icon", favicon)
|
||||||
|
})
|
||||||
|
|
||||||
secret, err := s.settingService.GetSecret()
|
secret, err := s.settingService.GetSecret()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -208,6 +217,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
s.server = controller.NewServerController(g)
|
s.server = controller.NewServerController(g)
|
||||||
s.xui = controller.NewXUIController(g)
|
s.xui = controller.NewXUIController(g)
|
||||||
s.api = controller.NewAPIController(g)
|
s.api = controller.NewAPIController(g)
|
||||||
|
s.sub = controller.NewSUBController(g)
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|||||||
63
x-ui.sh
63
x-ui.sh
@@ -20,46 +20,41 @@ function LOGI() {
|
|||||||
# check root
|
# check root
|
||||||
[[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1
|
[[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1
|
||||||
|
|
||||||
# check os
|
# Check OS and set release variable
|
||||||
if [[ -f /etc/redhat-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
release="centos"
|
source /etc/os-release
|
||||||
elif cat /etc/issue | grep -Eqi "debian"; then
|
release=$ID
|
||||||
release="debian"
|
elif [[ -f /usr/lib/os-release ]]; then
|
||||||
elif cat /etc/issue | grep -Eqi "ubuntu"; then
|
source /usr/lib/os-release
|
||||||
release="ubuntu"
|
release=$ID
|
||||||
elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then
|
|
||||||
release="centos"
|
|
||||||
elif cat /proc/version | grep -Eqi "debian"; then
|
|
||||||
release="debian"
|
|
||||||
elif cat /proc/version | grep -Eqi "ubuntu"; then
|
|
||||||
release="ubuntu"
|
|
||||||
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
|
|
||||||
release="centos"
|
|
||||||
else
|
else
|
||||||
LOGE "check system OS failed,please contact with author! \n" && exit 1
|
echo "Failed to check the system OS, please contact the author!" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "The OS release is: $release"
|
||||||
|
|
||||||
|
|
||||||
os_version=""
|
os_version=""
|
||||||
|
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
||||||
|
|
||||||
# os version
|
if [[ "${release}" == "centos" ]]; then
|
||||||
if [[ -f /etc/os-release ]]; then
|
|
||||||
os_version=$(awk -F'[= ."]' '/VERSION_ID/{print $3}' /etc/os-release)
|
|
||||||
fi
|
|
||||||
if [[ -z "$os_version" && -f /etc/lsb-release ]]; then
|
|
||||||
os_version=$(awk -F'[= ."]+' '/DISTRIB_RELEASE/{print $2}' /etc/lsb-release)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ x"${release}" == x"centos" ]]; then
|
|
||||||
if [[ ${os_version} -le 6 ]]; then
|
|
||||||
LOGE "please use CentOS 7 or higher version! \n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ x"${release}" == x"ubuntu" ]]; then
|
|
||||||
if [[ ${os_version} -lt 16 ]]; then
|
|
||||||
LOGE "please use Ubuntu 16 or higher version!\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ x"${release}" == x"debian" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
LOGE "please use Debian 8 or higher version!\n" && exit 1
|
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
elif [[ "${release}" == "ubuntu" ]]; then
|
||||||
|
if [[ ${os_version} -lt 20 ]]; then
|
||||||
|
echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "${release}" == "fedora" ]]; then
|
||||||
|
if [[ ${os_version} -lt 36 ]]; then
|
||||||
|
echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "${release}" == "debian" ]]; then
|
||||||
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
|
echo -e "${red} Please use Debian 8 or higher ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"x-ui/config"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
|
||||||
"github.com/Workiva/go-datastructures/queue"
|
"github.com/Workiva/go-datastructures/queue"
|
||||||
@@ -29,19 +30,19 @@ func GetBinaryName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetBinaryPath() string {
|
func GetBinaryPath() string {
|
||||||
return "bin/" + GetBinaryName()
|
return config.GetBinFolderPath() + "/" + GetBinaryName()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigPath() string {
|
func GetConfigPath() string {
|
||||||
return "bin/config.json"
|
return config.GetBinFolderPath() + "/config.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeositePath() string {
|
func GetGeositePath() string {
|
||||||
return "bin/geosite.dat"
|
return config.GetBinFolderPath() + "/geosite.dat"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeoipPath() string {
|
func GetGeoipPath() string {
|
||||||
return "bin/geoip.dat"
|
return config.GetBinFolderPath() + "/geoip.dat"
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProcess(p *Process) {
|
func stopProcess(p *Process) {
|
||||||
|
|||||||
Reference in New Issue
Block a user