mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-18 14:55:49 +00:00
Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
3d4b87d72f | ||
|
|
5fac7df174 | ||
|
|
8bba9f3c25 | ||
|
|
268e8c7eb1 | ||
|
|
14e8f6cbfa | ||
|
|
7f0ba495db | ||
|
|
af07a9bf15 | ||
|
|
6b8ca0c321 | ||
|
|
48554ab497 | ||
|
|
dddebf02ef | ||
|
|
0c469848dc | ||
|
|
cb85d4f83f | ||
|
|
df29570dc1 | ||
|
|
7e9c336a17 | ||
|
|
585f43498c | ||
|
|
2a7a09d12f | ||
|
|
98759e964c | ||
|
|
9ae3d62afb | ||
|
|
24b718dca3 | ||
|
|
d0c4d0a941 | ||
|
|
f829213dd1 | ||
|
|
4d5a73512e | ||
|
|
6dccc5b891 | ||
|
|
f642830dc8 | ||
|
|
073b2ee8a8 | ||
|
|
8bcfa0b101 | ||
|
|
cd9e903ec3 | ||
|
|
b9fecaa918 | ||
|
|
eb516575ec | ||
|
|
aeb5c9acaa | ||
|
|
68a7b38117 | ||
|
|
e084518f4f | ||
|
|
e6460d1d00 | ||
|
|
0218af8b4f | ||
|
|
552bc6c1f6 | ||
|
|
3001107282 | ||
|
|
ed6f45f341 | ||
|
|
83853a963e | ||
|
|
2774de744c | ||
|
|
7306be3cb6 | ||
|
|
b2d16b87bb | ||
|
|
4ab1135d89 | ||
|
|
8a6782273e | ||
|
|
b2075e9548 | ||
|
|
8efa776cd7 | ||
|
|
b641951759 | ||
|
|
f539cbec45 | ||
|
|
645b1535ac | ||
|
|
3729ac5e3e | ||
|
|
f736f069a0 | ||
|
|
cd3d42c7ee | ||
|
|
88cc4aed19 | ||
|
|
2ae1455a1f | ||
|
|
c20d13ea96 | ||
|
|
998d861d0a | ||
|
|
e1c76fdc7a | ||
|
|
6749d63222 | ||
|
|
546b66a61c | ||
|
|
2e2f066c4b | ||
|
|
5bac00b419 | ||
|
|
d07a2119ce | ||
|
|
2951b5f94c | ||
|
|
a9c25cddb3 | ||
|
|
edc911c2f3 | ||
|
|
c053991a37 | ||
|
|
73803af14c | ||
|
|
6dc823e215 | ||
|
|
5b1eaf9cc3 |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
10
.github/ISSUE_TEMPLATE/question-template.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/question-template.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: Question template
|
||||||
|
about: Ask if it is not clear that it is a bug
|
||||||
|
title: ''
|
||||||
|
labels: question
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
14
.github/workflows/release.yml
vendored
14
.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@v3
|
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
|
||||||
@@ -46,11 +46,11 @@ 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
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.20'
|
||||||
- name: build linux arm64 version
|
- name: build linux arm64 version
|
||||||
@@ -86,11 +86,11 @@ 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
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.20'
|
||||||
- name: build linux s390x version
|
- name: build linux s390x version
|
||||||
|
|||||||
108
README.md
108
README.md
@@ -6,20 +6,20 @@
|
|||||||
[](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**
|
||||||
|
|
||||||
|
|
||||||
# Temporary downgrade to 0.2.4 due to fixing bugs
|
|
||||||
Coming back soon with new features
|
|
||||||
|
|
||||||
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: |
|
||||||
| 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 | :heavy_check_mark: |
|
| Telegram BOT (admin + clients) | :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:
|
||||||
|
|
||||||
@@ -27,25 +27,61 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
|||||||
|
|
||||||
- System Status Monitoring
|
- System Status Monitoring
|
||||||
- Search within all inbounds and clients
|
- Search within all inbounds and clients
|
||||||
|
- Support Dark/Light theme UI
|
||||||
- Support multi-user multi-protocol, web page visualization operation
|
- Support multi-user multi-protocol, web page visualization operation
|
||||||
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
||||||
- 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 |
|
||||||
|
|
||||||
# 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.1`:
|
||||||
|
```
|
||||||
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.1
|
||||||
|
```
|
||||||
|
|
||||||
## 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`
|
||||||
@@ -68,8 +104,6 @@ systemctl restart x-ui
|
|||||||
|
|
||||||
## Install using docker
|
## Install using docker
|
||||||
|
|
||||||
> This docker tutorial and docker image are provided by [alireza0](https://github.com/alireza0)
|
|
||||||
|
|
||||||
1. install docker
|
1. install docker
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -80,11 +114,13 @@ curl -fsSL https://get.docker.com | sh
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
mkdir x-ui && cd x-ui
|
mkdir x-ui && cd x-ui
|
||||||
docker run -itd --network=host \
|
docker run -itd \
|
||||||
|
-p 54321:54321 -p 443:443 -p 80:80 \
|
||||||
|
-e XRAY_VMESS_AEAD_FORCED=false \
|
||||||
-v $PWD/db/:/etc/x-ui/ \
|
-v $PWD/db/:/etc/x-ui/ \
|
||||||
-v $PWD/cert/:/root/cert/ \
|
-v $PWD/cert/:/root/cert/ \
|
||||||
--name x-ui --restart=unless-stopped \
|
--name x-ui --restart=unless-stopped \
|
||||||
alireza0/x-ui:latest
|
alireza7/x-ui:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
> Build your own image
|
> Build your own image
|
||||||
@@ -94,6 +130,8 @@ docker build -t x-ui .
|
|||||||
```
|
```
|
||||||
|
|
||||||
## SSL certificate application
|
## SSL certificate application
|
||||||
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
### Cloudflare
|
### Cloudflare
|
||||||
|
|
||||||
@@ -108,43 +146,52 @@ 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 (under development, temporarily unavailable)
|
## Tg robot use
|
||||||
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
- Tg Robot Token
|
- Tg robot Token
|
||||||
- Tg Robot ChatId
|
- Tg robot ChatId
|
||||||
- Tg robot cycle runtime, in crontab syntax
|
- Tg robot cycle runtime, in crontab syntax
|
||||||
|
- Tg robot Expiration threshold
|
||||||
|
- Tg robot Traffic threshold
|
||||||
|
- Tg robot Enable send backup in cycle runtime
|
||||||
|
- Tg robot Enable CPU usage alarm threshold
|
||||||
|
|
||||||
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
|
||||||
- TG notification content:
|
|
||||||
|
|
||||||
- Node traffic usage
|
### Telegram Bot Features
|
||||||
- Panel login reminder
|
|
||||||
- Node expiration reminder
|
|
||||||
- Traffic warning reminder
|
|
||||||
|
|
||||||
More features are planned...
|
- Report periodic
|
||||||
|
- Login notification
|
||||||
|
- CPU threshold notification
|
||||||
## suggestion system
|
- Threshold for Expiration time and Traffic to report in advance
|
||||||
|
- Support client report menu if client's telegram username added to the user's configurations
|
||||||
- CentOS 7+
|
- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
||||||
- Ubuntu 16+
|
- Menu based bot
|
||||||
- Debian 8+
|
- Search client by email ( only admin )
|
||||||
|
- Check all inbounds
|
||||||
# common problem
|
- Check server status
|
||||||
|
- Check depleted users
|
||||||
|
- Receive backup by request and in periodic reports
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Common problem
|
||||||
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
## 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`
|
||||||
@@ -174,7 +221,6 @@ find this in config :
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
**the final output is like :**
|
**the final output is like :**
|
||||||
```json
|
```json
|
||||||
"policy": {
|
"policy": {
|
||||||
@@ -193,10 +239,10 @@ 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
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.3.2
|
0.5.2
|
||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
db/x-ui.db
BIN
db/x-ui.db
Binary file not shown.
29
go.mod
29
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.1
|
github.com/shirou/gopsutil/v3 v3.23.3
|
||||||
github.com/xtls/xray-core v1.7.5
|
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.7.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.5
|
gorm.io/gorm v1.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -29,15 +30,14 @@ 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.2 // indirect
|
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
github.com/gorilla/sessions v1.2.1 // indirect
|
github.com/gorilla/sessions v1.2.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
@@ -46,16 +46,17 @@ 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
|
||||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
golang.org/x/crypto v0.5.0 // indirect
|
golang.org/x/crypto v0.7.0 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 // indirect
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.29.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
99
go.sum
99
go.sum
@@ -3,7 +3,7 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak
|
|||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
||||||
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
@@ -44,13 +44,13 @@ 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=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
@@ -58,7 +58,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
|
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
@@ -70,17 +70,16 @@ 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=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
@@ -96,6 +95,7 @@ github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
|
|||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
|
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -104,7 +104,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
|
github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
@@ -119,21 +119,24 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
|
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
|
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
||||||
github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
|
github.com/refraction-networking/utls v1.2.3-0.20230308205431-4f1df6c200db h1:ULRv/GPW5KYDafE0FACN2no+HTCyQLUtfyOIeyp3GNc=
|
||||||
github.com/refraction-networking/utls v1.2.2-0.20230207151345-a75a4b484849 h1:vNEcNapWFwnYJTBcVkHJa8VrdL40PNDLDbSGVY+ZV7I=
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/sagernet/sing v0.1.6 h1:Qy63OUfKpcqKjfd5rPmUlj0RGjHZSK/PJn0duyCCsRg=
|
github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08=
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7 h1:Plup6oEiyLzY3HDqQ+QsUBzgBGdVmcsgf3t8h940z9U=
|
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.1 h1:a9KKO+kGLKEvcPIs4W62v0nu3sciVDOOOPUD0Hz7z/4=
|
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.1/go.mod h1:NN6mnm5/0k8jw4cBfCnJtr5L7ErOTg18tMNpgFkn0hA=
|
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=
|
||||||
@@ -143,8 +146,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||||
@@ -158,14 +162,14 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
|
|||||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3 h1:a3Y4WVjCxwoyO4E2xdNvq577tW8lkSBgyrA8E9+2NtM=
|
github.com/xtls/reality v0.0.0-20230309125256-0d0713b108c8 h1:LLtLxEe3S0Ko+ckqt4t29RLskpNdOZfgjZCC2/Byr50=
|
||||||
github.com/xtls/xray-core v1.7.5 h1:Ukr3hXnOG2ciViQL7kfYRl9S3GVej2dkV7DzabmoLL4=
|
github.com/xtls/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k=
|
||||||
github.com/xtls/xray-core v1.7.5/go.mod h1:Mx1QzIDvSk4eZ8hKa3AYsSPfyZJNQXWVXTJxJRJ98wI=
|
github.com/xtls/xray-core v1.8.0/go.mod h1:i9KWgbLyxg/NT+3+g4nE74Zp3DgTCP3X04YkSfsJeDI=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.starlark.net v0.0.0-20230128213706-3f75dec8e403 h1:jPeC7Exc+m8OBJUlWbBLh0O5UZPM7yU5W4adnhhbG4U=
|
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||||
@@ -174,19 +178,19 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
|
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -204,9 +208,8 @@ 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.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.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=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -214,26 +217,26 @@ 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
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=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
|
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 h1:vArvWooPH749rNHpBGgVl+U9B9dATjiEhJzcWGlovNs=
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||||
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@@ -243,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.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
|
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||||
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
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
|
||||||
|
|||||||
10
main.go
10
main.go
@@ -136,7 +136,7 @@ func updateTgbotEnableSts(status bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTgbotSetting(tgBotToken string, tgBotChatid int, tgBotRuntime string) {
|
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
||||||
err := database.InitDB(config.GetDBPath())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -165,7 +165,7 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid int, tgBotRuntime string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tgBotChatid != 0 {
|
if tgBotChatid != "" {
|
||||||
err := settingService.SetTgBotChatId(tgBotChatid)
|
err := settingService.SetTgBotChatId(tgBotChatid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -224,7 +224,7 @@ func main() {
|
|||||||
var username string
|
var username string
|
||||||
var password string
|
var password string
|
||||||
var tgbottoken string
|
var tgbottoken string
|
||||||
var tgbotchatid int
|
var tgbotchatid string
|
||||||
var enabletgbot bool
|
var enabletgbot bool
|
||||||
var tgbotRuntime string
|
var tgbotRuntime string
|
||||||
var reset bool
|
var reset bool
|
||||||
@@ -236,7 +236,7 @@ func main() {
|
|||||||
settingCmd.StringVar(&password, "password", "", "set login password")
|
settingCmd.StringVar(&password, "password", "", "set login password")
|
||||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token")
|
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token")
|
||||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time")
|
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time")
|
||||||
settingCmd.IntVar(&tgbotchatid, "tgbotchatid", 0, "set telegrame bot chat id")
|
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegrame bot chat id")
|
||||||
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
|
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
|
||||||
|
|
||||||
oldUsage := flag.Usage
|
oldUsage := flag.Usage
|
||||||
@@ -287,7 +287,7 @@ func main() {
|
|||||||
if show {
|
if show {
|
||||||
showSetting(show)
|
showSetting(show)
|
||||||
}
|
}
|
||||||
if (tgbottoken != "") || (tgbotchatid != 0) || (tgbotRuntime != "") {
|
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
||||||
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
BIN
media/inbounds-dark.png
Normal file
BIN
media/inbounds-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 324 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 316 KiB |
2
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
2
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -150,3 +150,237 @@
|
|||||||
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-thead>tr>th{
|
||||||
|
padding:16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-expand-icon-th,
|
||||||
|
.ant-table-row-expand-icon-cell {
|
||||||
|
width: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-dark,
|
||||||
|
.ant-menu-dark .ant-menu-sub,
|
||||||
|
.ant-layout-header,
|
||||||
|
.ant-layout-sider-dark,
|
||||||
|
.ant-layout-sider-zero-width-trigger,
|
||||||
|
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
||||||
|
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
||||||
|
background:#161b22
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #1a212a;
|
||||||
|
border-color:rgba(0,0,0,.09);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark:hover {
|
||||||
|
border-color: #e8e8e8;
|
||||||
|
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-table-thead th {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #161b22;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-table-tbody tr td,
|
||||||
|
.ant-card-dark .ant-modal-title {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-collapse-content,
|
||||||
|
.ant-card-dark .ant-calendar,
|
||||||
|
.ant-card-dark .ant-table-placeholder,
|
||||||
|
.ant-card-dark .ant-input-group-addon {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #262f3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-list-item-meta-title,
|
||||||
|
.ant-card-dark .ant-list-item-meta-description,
|
||||||
|
.ant-card-dark .ant-form-item-label>label,
|
||||||
|
.ant-card-dark .ant-form-item,
|
||||||
|
.ant-card-dark .ant-divider-inner-text,
|
||||||
|
.ant-card-dark .ant-modal-confirm-content,
|
||||||
|
.ant-card-dark .ant-modal-confirm-title,
|
||||||
|
.ant-card-dark .ant-progress-text,
|
||||||
|
.ant-card-dark .ant-modal-close,
|
||||||
|
.ant-card-dark i,
|
||||||
|
.ant-card-dark .ant-select-dropdown-menu-item,
|
||||||
|
.ant-card-dark .ant-calendar-month-select,
|
||||||
|
.ant-card-dark .ant-calendar-year-select,
|
||||||
|
.ant-card-dark .ant-calendar-date,
|
||||||
|
.ant-card-dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,
|
||||||
|
.ant-card-dark .ant-empty-normal,
|
||||||
|
.ant-card-dark .ant-checkbox+span {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
|
||||||
|
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
||||||
|
.ant-card-dark .ant-calendar-date:hover,
|
||||||
|
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
||||||
|
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
||||||
|
background-color: #004488;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark tbody .ant-table-expanded-row,
|
||||||
|
.ant-card-dark .ant-calendar-time-picker-inner {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #1a212a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-input,
|
||||||
|
.ant-card-dark .ant-input-number,
|
||||||
|
.ant-card-dark .ant-input-number-handler-wrap,
|
||||||
|
.ant-card-dark .ant-calendar-input,
|
||||||
|
.ant-card-dark .ant-select-dropdown-menu-item-selected,
|
||||||
|
.ant-card-dark .ant-select-selection,
|
||||||
|
.ant-card-dark .ant-calendar-picker-clear {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #2e3b52;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-collapse-item {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
background-color: #161b22;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark,
|
||||||
|
.ant-card-dark .ant-modal-content {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.65);
|
||||||
|
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-modal-content,
|
||||||
|
.ant-card-dark .ant-modal-body,
|
||||||
|
.ant-card-dark .ant-modal-header,
|
||||||
|
.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,10 +170,14 @@ 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 = 0;
|
this.tgBotChatId = "";
|
||||||
this.tgRunTime = "";
|
this.tgRunTime = "@daily";
|
||||||
|
this.tgBotBackup = false;
|
||||||
|
this.tgCpu = "";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ const UTLS_FINGERPRINT = {
|
|||||||
UTLS_RANDOMIZED: "randomized",
|
UTLS_RANDOMIZED: "randomized",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ALPN_OPTION = {
|
||||||
|
H3: "h3",
|
||||||
|
H2: "h2",
|
||||||
|
HTTP1: "http/1.1",
|
||||||
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
Object.freeze(VmessMethods);
|
Object.freeze(VmessMethods);
|
||||||
Object.freeze(SSMethods);
|
Object.freeze(SSMethods);
|
||||||
@@ -101,6 +107,7 @@ 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);
|
||||||
|
Object.freeze(ALPN_OPTION);
|
||||||
|
|
||||||
class XrayCommonClass {
|
class XrayCommonClass {
|
||||||
|
|
||||||
@@ -467,11 +474,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=["h2", "http/1.1"]) {
|
alpn=[],
|
||||||
|
settings=[new TlsStreamSettings.Settings()]) {
|
||||||
super();
|
super();
|
||||||
this.server = serverName;
|
this.server = serverName;
|
||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
@@ -479,6 +487,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
this.cipherSuites = cipherSuites;
|
this.cipherSuites = cipherSuites;
|
||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCert(cert) {
|
addCert(cert) {
|
||||||
@@ -491,17 +500,23 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
let certs;
|
let certs;
|
||||||
|
let settings;
|
||||||
if (!ObjectUtil.isEmpty(json.certificates)) {
|
if (!ObjectUtil.isEmpty(json.certificates)) {
|
||||||
certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert));
|
certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
|
let values = json.settings[0];
|
||||||
|
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
|
||||||
|
}
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
json.minVersion,
|
json.minVersion,
|
||||||
json.maxVersion,
|
json.maxVersion,
|
||||||
json.cipherSuites,
|
json.cipherSuites,
|
||||||
certs,
|
certs,
|
||||||
json.alpn
|
json.alpn,
|
||||||
|
settings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,7 +527,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
maxVersion: this.maxVersion,
|
maxVersion: this.maxVersion,
|
||||||
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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,6 +574,29 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
|
constructor(allowInsecure = false, fingerprint = '', serverName = '') {
|
||||||
|
super();
|
||||||
|
this.allowInsecure = allowInsecure;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
this.serverName = serverName;
|
||||||
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new TlsStreamSettings.Settings(
|
||||||
|
json.allowInsecure,
|
||||||
|
json.fingerprint,
|
||||||
|
json.servername,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
allowInsecure: this.allowInsecure,
|
||||||
|
fingerprint: this.fingerprint,
|
||||||
|
serverName: this.serverName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class StreamSettings extends XrayCommonClass {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
@@ -881,16 +920,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;
|
||||||
@@ -920,7 +959,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is used for xtls-rprx-vison
|
//this is used for xtls-rprx-vision
|
||||||
canEnableTlsFlow() {
|
canEnableTlsFlow() {
|
||||||
if ((this.stream.security === 'tls') && (this.network === "tcp")) {
|
if ((this.stream.security === 'tls') && (this.network === "tcp")) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
@@ -1042,6 +1081,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[0]['serverName'],
|
||||||
|
fp: this.stream.tls.settings[0]['fingerprint'],
|
||||||
|
alpn: this.stream.tls.alpn.join(','),
|
||||||
|
allowInsecure: this.stream.tls.settings[0].allowInsecure,
|
||||||
};
|
};
|
||||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||||
}
|
}
|
||||||
@@ -1053,12 +1096,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);
|
||||||
if (this.xtls) {
|
|
||||||
params.set("security", "xtls");
|
|
||||||
address = this.stream.tls.server;
|
|
||||||
} else {
|
|
||||||
params.set("security", this.stream.security);
|
|
||||||
}
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
const tcp = this.stream.tcp;
|
const tcp = this.stream.tcp;
|
||||||
@@ -1104,15 +1141,33 @@ class Inbound extends XrayCommonClass {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stream.security === 'tls') {
|
if (this.tls) {
|
||||||
|
params.set("security", "tls");
|
||||||
|
params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
|
||||||
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
|
if(this.stream.tls.settings[0].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;
|
||||||
params.set("sni", address);
|
}
|
||||||
|
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
||||||
|
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
||||||
|
}
|
||||||
|
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.xtls) {
|
||||||
|
params.set("security", "xtls");
|
||||||
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
|
if(this.stream.tls.settings[0].allowInsecure){
|
||||||
|
params.set("allowInsecure", "1");
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
|
address = this.stream.tls.server;
|
||||||
|
}
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1145,11 +1200,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);
|
||||||
if (this.xtls) {
|
|
||||||
params.set("security", "xtls");
|
|
||||||
} else {
|
|
||||||
params.set("security", this.stream.security);
|
|
||||||
}
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
const tcp = this.stream.tcp;
|
const tcp = this.stream.tcp;
|
||||||
@@ -1195,16 +1245,33 @@ class Inbound extends XrayCommonClass {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stream.security === 'tls') {
|
if (this.tls) {
|
||||||
|
params.set("security", "tls");
|
||||||
|
params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
|
||||||
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
|
if(this.stream.tls.settings[0].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;
|
||||||
params.set("sni", address);
|
|
||||||
}
|
}
|
||||||
|
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
||||||
|
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.xtls) {
|
||||||
|
params.set("security", "xtls");
|
||||||
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
|
if(this.stream.tls.settings[0].allowInsecure){
|
||||||
|
params.set("allowInsecure", "1");
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
|
address = this.stream.tls.server;
|
||||||
|
}
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||||
}
|
}
|
||||||
if (this.xtls) {
|
|
||||||
params.set("flow", this.settings.trojans[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)}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
@@ -1218,18 +1285,18 @@ class Inbound extends XrayCommonClass {
|
|||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
if (this.settings.vmesses[clientIndex].email != ""){
|
if (this.settings.vmesses[clientIndex].email != ""){
|
||||||
remark += '-' + this.settings.vmesses[clientIndex].email
|
remark = this.settings.vmesses[clientIndex].email
|
||||||
}
|
}
|
||||||
return this.genVmessLink(address, remark, clientIndex);
|
return this.genVmessLink(address, remark, clientIndex);
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
if (this.settings.vlesses[clientIndex].email != ""){
|
if (this.settings.vlesses[clientIndex].email != ""){
|
||||||
remark += '-' + this.settings.vlesses[clientIndex].email
|
remark = this.settings.vlesses[clientIndex].email
|
||||||
}
|
}
|
||||||
return this.genVLESSLink(address, remark, clientIndex);
|
return this.genVLESSLink(address, remark, clientIndex);
|
||||||
case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark);
|
case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark);
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if (this.settings.trojans[clientIndex].email != ""){
|
if (this.settings.trojans[clientIndex].email != ""){
|
||||||
remark += '-' + this.settings.trojans[clientIndex].email
|
remark = this.settings.trojans[clientIndex].email
|
||||||
}
|
}
|
||||||
return this.genTrojanLink(address, remark, clientIndex);
|
return this.genTrojanLink(address, remark, clientIndex);
|
||||||
default: return '';
|
default: return '';
|
||||||
@@ -1365,13 +1432,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={}) {
|
||||||
@@ -1381,13 +1451,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1440,22 +1515,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={}) {
|
||||||
@@ -1465,6 +1541,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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1472,6 +1551,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1561,13 +1643,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() {
|
||||||
@@ -1577,6 +1662,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1587,7 +1675,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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1595,6 +1685,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1685,11 +1778,12 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol, address, port, network='tcp,udp') {
|
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.network = network;
|
this.network = network;
|
||||||
|
this.followRedirect = followRedirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -1698,6 +1792,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||||||
json.address,
|
json.address,
|
||||||
json.port,
|
json.port,
|
||||||
json.network,
|
json.network,
|
||||||
|
json.followRedirect,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1706,6 +1801,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||||||
address: this.address,
|
address: this.address,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
network: this.network,
|
network: this.network,
|
||||||
|
followRedirect: this.followRedirect,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||||||
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/job"
|
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
||||||
@@ -20,6 +19,7 @@ type IndexController struct {
|
|||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
userService service.UserService
|
userService service.UserService
|
||||||
|
tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIndexController(g *gin.RouterGroup) *IndexController {
|
func NewIndexController(g *gin.RouterGroup) *IndexController {
|
||||||
@@ -60,13 +60,13 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
user := a.userService.CheckUser(form.Username, form.Password)
|
user := a.userService.CheckUser(form.Username, form.Password)
|
||||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||||
if user == nil {
|
if user == nil {
|
||||||
job.NewStatsNotifyJob().UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.Infof("%s login success,Ip Address:%s\n", form.Username, getRemoteIp(c))
|
logger.Infof("%s login success,Ip Address:%s\n", form.Username, getRemoteIp(c))
|
||||||
job.NewStatsNotifyJob().UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = session.SetLoginUser(c, user)
|
err = session.SetLoginUser(c, user)
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -34,7 +35,12 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.Use(a.checkLogin)
|
g.Use(a.checkLogin)
|
||||||
g.POST("/status", a.status)
|
g.POST("/status", a.status)
|
||||||
g.POST("/getXrayVersion", a.getXrayVersion)
|
g.POST("/getXrayVersion", a.getXrayVersion)
|
||||||
|
g.POST("/stopXrayService", a.stopXrayService)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
@@ -83,3 +89,56 @@ func (a *ServerController) installXray(c *gin.Context) {
|
|||||||
err := a.serverService.UpdateXray(version)
|
err := a.serverService.UpdateXray(version)
|
||||||
jsonMsg(c, I18n(c, "install")+" xray", err)
|
jsonMsg(c, I18n(c, "install")+" xray", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) stopXrayService(c *gin.Context) {
|
||||||
|
a.lastGetStatusTime = time.Now()
|
||||||
|
err := a.serverService.StopXrayService()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(c, "Xray stoped", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
func (a *ServerController) restartXrayService(c *gin.Context) {
|
||||||
|
err := a.serverService.RestartXrayService()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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, I18n(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, I18n(c, "getLogs"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, configJson, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getDb(c *gin.Context) {
|
||||||
|
db, err := a.serverService.GetDb()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "getLogs"), 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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"time"
|
"time"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type updateUserForm struct {
|
type updateUserForm struct {
|
||||||
@@ -32,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)
|
||||||
@@ -46,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,16 @@ 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 int `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"`
|
||||||
|
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
|
|||||||
@@ -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,6 +1,7 @@
|
|||||||
{{define "promptModal"}}
|
{{define "promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
{{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"
|
||||||
cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}">
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
<canvas id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
:footer="null"
|
||||||
|
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>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -12,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 {
|
||||||
@@ -30,12 +31,6 @@
|
|||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
qrModalApp.$nextTick(() => {
|
qrModalApp.$nextTick(() => {
|
||||||
if (this.clipboard === null) {
|
|
||||||
this.clipboard = new ClipboardJS('#qr-modal-ok-btn', {
|
|
||||||
text: () => this.copyText,
|
|
||||||
});
|
|
||||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
|
||||||
}
|
|
||||||
if (this.qrcode === null) {
|
if (this.qrcode === null) {
|
||||||
this.qrcode = new QRious({
|
this.qrcode = new QRious({
|
||||||
element: document.querySelector('#qrCode'),
|
element: document.querySelector('#qrCode'),
|
||||||
@@ -57,6 +52,17 @@
|
|||||||
data: {
|
data: {
|
||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
copyToClipboard() {
|
||||||
|
this.qrModal.clipboard = new ClipboardJS('#qrCode', {
|
||||||
|
text: () => this.qrModal.copyText,
|
||||||
|
});
|
||||||
|
this.qrModal.clipboard.on('success', () => {
|
||||||
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
|
this.qrModal.clipboard.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{{define "textModal"}}
|
{{define "textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :download="txtModal.fileName">
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :download="txtModal.fileName">
|
||||||
|
|||||||
@@ -1,12 +1,44 @@
|
|||||||
{{define "clientsBulkModal"}}
|
{{define "clientsBulkModal"}}
|
||||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
||||||
:closable="true" :mask-closable="false"
|
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
: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>
|
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
||||||
|
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option :value="0">Random</a-select-option>
|
||||||
|
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||||
|
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||||
|
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
||||||
|
<a-select-option :value="4">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item><br />
|
||||||
|
<a-form-item v-if="clientsBulkModal.emailMethod>1">
|
||||||
|
<span slot="label">{{ i18n "pages.client.first" }}</span>
|
||||||
|
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.emailMethod>1">
|
||||||
|
<span slot="label">{{ i18n "pages.client.last" }}</span>
|
||||||
|
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.emailMethod>0">
|
||||||
|
<span slot="label">{{ i18n "pages.client.prefix" }}</span>
|
||||||
|
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 120px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.emailMethod>2">
|
||||||
|
<span slot="label">{{ i18n "pages.client.postfix" }}</span>
|
||||||
|
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 120px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.emailMethod < 2">
|
||||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
||||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Subscription">
|
||||||
|
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Telegram ID">
|
||||||
|
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||||
@@ -19,7 +51,13 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item label="{{ i18n "pages.client.delayedStart" }}">
|
||||||
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientsBulkModal.delayedStart">
|
||||||
|
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-else>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -30,7 +68,8 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||||
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -38,6 +77,7 @@
|
|||||||
|
|
||||||
const clientsBulkModal = {
|
const clientsBulkModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
title: '',
|
title: '',
|
||||||
okText: '',
|
okText: '',
|
||||||
confirm: null,
|
confirm: null,
|
||||||
@@ -47,9 +87,32 @@
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
totalGB: 0,
|
totalGB: 0,
|
||||||
expiryTime: '',
|
expiryTime: '',
|
||||||
|
emailMethod: 0,
|
||||||
|
firstNum: 1,
|
||||||
|
lastNum: 1,
|
||||||
|
emailPrefix: "",
|
||||||
|
emailPostfix: "",
|
||||||
|
subId: "",
|
||||||
|
tgId: "",
|
||||||
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
for (let i = 0; i < clientsBulkModal.quantity; i++) {
|
method=clientsBulkModal.emailMethod;
|
||||||
|
if(method>1){
|
||||||
|
start=clientsBulkModal.firstNum;
|
||||||
|
end=clientsBulkModal.lastNum + 1;
|
||||||
|
} else {
|
||||||
|
start=0;
|
||||||
|
end=clientsBulkModal.quantity;
|
||||||
|
}
|
||||||
|
prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : "";
|
||||||
|
useNum=(method>1);
|
||||||
|
postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? clientsBulkModal.emailPostfix : "";
|
||||||
|
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.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);
|
clientsBulkModal.clients.push(newClient);
|
||||||
@@ -63,10 +126,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.firstNum= 1;
|
||||||
|
this.lastNum= 1;
|
||||||
|
this.emailPrefix= "";
|
||||||
|
this.emailPostfix= "";
|
||||||
|
this.subId= "";
|
||||||
|
this.tgId= "";
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
||||||
|
this.delayedStart = false;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
switch(protocol){
|
switch(protocol){
|
||||||
@@ -101,6 +172,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>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{{define "clientsModal"}}
|
{{define "clientsModal"}}
|
||||||
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
||||||
:closable="true" :mask-closable="false"
|
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -8,6 +9,7 @@
|
|||||||
|
|
||||||
const clientModal = {
|
const clientModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
title: '',
|
title: '',
|
||||||
okText: '',
|
okText: '',
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
@@ -16,6 +18,7 @@
|
|||||||
clientStats: [],
|
clientStats: [],
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index);
|
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index);
|
||||||
},
|
},
|
||||||
@@ -29,8 +32,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;
|
||||||
@@ -79,7 +87,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
|
||||||
},
|
},
|
||||||
@@ -88,10 +96,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) {
|
||||||
|
|||||||
@@ -33,27 +33,50 @@
|
|||||||
|
|
||||||
|
|
||||||
{{define "commonSider"}}
|
{{define "commonSider"}}
|
||||||
<a-layout-sider id="sider" collapsible breakpoint="md" collapsed-width="0">
|
<a-layout-sider :theme="siderDrawer.theme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||||
<a-menu theme="dark" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="siderDrawer.theme" mode="inline" selected-keys="">
|
||||||
|
<a-menu-item mode="inline">
|
||||||
|
<a-icon type="bg-colors"></a-icon>
|
||||||
|
<a-switch :default-checked="siderDrawer.isDarkTheme"
|
||||||
|
checked-children="☀"
|
||||||
|
un-checked-children="🌙"
|
||||||
|
@change="siderDrawer.changeTheme()"></a-switch>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
<a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
||||||
@close="siderDrawer.close()"
|
@close="siderDrawer.close()"
|
||||||
:visible="siderDrawer.visible" :wrap-style="{ padding: 0 }">
|
:visible="siderDrawer.visible"
|
||||||
|
:wrap-class-name="siderDrawer.isDarkTheme ? 'ant-drawer-dark' : ''"
|
||||||
|
:wrap-style="{ padding: 0 }">
|
||||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||||
</div>
|
</div>
|
||||||
<a-menu theme="light" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="siderDrawer.theme" mode="inline" selected-keys="">
|
||||||
|
<a-menu-item mode="inline">
|
||||||
|
<a-icon type="bg-colors"></a-icon>
|
||||||
|
<a-switch :default-checked="siderDrawer.isDarkTheme"
|
||||||
|
checked-children="☀"
|
||||||
|
un-checked-children="🌙"
|
||||||
|
@change="siderDrawer.changeTheme()"></a-switch>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
<a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
<script>
|
<script>
|
||||||
|
const darkClass = "ant-card-dark";
|
||||||
|
const bgDarkStyle = "background-color: #242c3a";
|
||||||
const siderDrawer = {
|
const siderDrawer = {
|
||||||
visible: false,
|
visible: false,
|
||||||
|
collapsed: false,
|
||||||
|
isDarkTheme: localStorage.getItem("dark-mode") === 'true' ? true : false,
|
||||||
show() {
|
show() {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
@@ -62,6 +85,16 @@
|
|||||||
},
|
},
|
||||||
change() {
|
change() {
|
||||||
this.visible = !this.visible;
|
this.visible = !this.visible;
|
||||||
|
},
|
||||||
|
toggleCollapsed() {
|
||||||
|
this.collapsed = !this.collapsed;
|
||||||
|
},
|
||||||
|
changeTheme() {
|
||||||
|
this.isDarkTheme = ! this.isDarkTheme;
|
||||||
|
localStorage.setItem("dark-mode", this.isDarkTheme);
|
||||||
|
},
|
||||||
|
get theme() {
|
||||||
|
return this.isDarkTheme ? 'dark' : 'light';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,19 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
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>
|
</span>
|
||||||
<a-input v-model.trim="client.email"></a-input>
|
<a-input v-model.trim="client.email"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="{{ i18n "pages.inbounds.enable" }}">
|
||||||
|
<a-switch v-model="client.enable"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="password" v-if="inbound.protocol === Protocols.TROJAN">
|
<a-form-item label="password" v-if="inbound.protocol === Protocols.TROJAN">
|
||||||
<a-input v-model.trim="client.password"></a-input>
|
<a-input v-model.trim="client.password"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -24,8 +27,14 @@
|
|||||||
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
||||||
<a-input type="number" v-model.number="client.alterId"></a-input>
|
<a-input type="number" v-model.number="client.alterId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Subscription" v-if="client.email">
|
||||||
|
<a-input v-model.trim="client.subId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Telegram Username" v-if="client.email">
|
||||||
|
<a-input v-model.trim="client.tgId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.xtls" label="flow">
|
<a-form-item v-if="inbound.xtls" label="flow">
|
||||||
<a-select v-model="client.flow" style="width: 150px">
|
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -48,7 +57,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
<template v-if="isEdit && clientStats">
|
<template v-if="isEdit && clientStats">
|
||||||
{{ i18n "usage" }}:
|
<span>{{ i18n "usage" }}:</span>
|
||||||
<a-tag :color="statsColor">
|
<a-tag :color="statsColor">
|
||||||
[[ sizeFormat(clientStats.up) ]] /
|
[[ sizeFormat(clientStats.up) ]] /
|
||||||
[[ sizeFormat(clientStats.down) ]]
|
[[ sizeFormat(clientStats.down) ]]
|
||||||
@@ -56,7 +65,13 @@
|
|||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item label="{{ i18n "pages.client.delayedStart" }}">
|
||||||
|
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientModal.delayedStart">
|
||||||
|
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-else>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -67,7 +82,8 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
||||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
<a-form-item label='{{ i18n "protocol" }}'>
|
||||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit">
|
<a-select v-model="inbound.protocol" style="width: 160px;" :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-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<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: 300px;"></a-date-picker>
|
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -7,11 +7,14 @@
|
|||||||
<a-input type="number" v-model.number="inbound.settings.port"></a-input>
|
<a-input type="number" v-model.number="inbound.settings.port"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;">
|
<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,udp">tcp+udp</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="FollowRedirect">
|
||||||
|
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 165px;">
|
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
<a-input v-model.trim="inbound.settings.password"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;">
|
<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,udp">tcp+udp</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
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>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<a-input v-model.trim="client.password"></a-input>
|
<a-input v-model.trim="client.password"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.xtls" label="flow">
|
<a-form-item v-if="inbound.xtls" label="flow">
|
||||||
<a-select v-model="client.flow" style="width: 150px">
|
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -48,23 +48,24 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr style="background-color: lightgrey;">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.trojans[0]).slice(0, 3)">[[ col ]]</th>
|
<th v-for="col in Object.keys(inbound.settings.trojans[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.trojans" :style="[ index % 2 == 1 ? {'background-color': '#fafafa'} : {}]">
|
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && inbound.tls">
|
<template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-row>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
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>
|
||||||
@@ -20,13 +20,13 @@
|
|||||||
<a-input v-model.trim="client.id"></a-input>
|
<a-input v-model.trim="client.id"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.xtls" label="flow">
|
<a-form-item v-if="inbound.xtls" label="flow">
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px">
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline">
|
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline">
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px">
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<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: 300px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
@@ -61,16 +62,16 @@
|
|||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr style="background-color: lightgrey;">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.vlesses[0]).slice(0, 3)">[[ col ]]</th>
|
<th v-for="col in Object.keys(inbound.settings.vlesses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.vlesses" :style="[ index % 2 == 1 ? {'background-color': '#fafafa'} : {}]">
|
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && inbound.tls">
|
<template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-row>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
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>
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<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: 300px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
@@ -52,10 +53,10 @@
|
|||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vmesses.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vmesses.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr style="background-color: lightgrey;">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.vmesses[0]).slice(0, 3)">[[ col ]]</th>
|
<th v-for="col in Object.keys(inbound.settings.vmesses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.vmesses" :style="[ index % 2 == 1 ? {'background-color': '#fafafa'} : {}]">
|
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
<a-select-option value="none">none(not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.security" style="width: 165px;">
|
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="none">none</a-select-option>
|
<a-select-option value="none">none</a-select-option>
|
||||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;">
|
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
<a-select-option value="none">none(not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange">
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
<a-select-option value="kcp">kcp</a-select-option>
|
<a-select-option value="kcp">kcp</a-select-option>
|
||||||
<a-select-option value="ws">ws</a-select-option>
|
<a-select-option value="ws">ws</a-select-option>
|
||||||
|
|||||||
@@ -12,15 +12,8 @@
|
|||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<a-form v-if="inbound.tls || inbound.xtls" layout="inline">
|
<a-form v-if="inbound.tls || inbound.xtls" layout="inline">
|
||||||
<a-form-item label="MinVersion">
|
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls">
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px">
|
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input>
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="MaxVersion">
|
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px">
|
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="CipherSuites">
|
<a-form-item label="CipherSuites">
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
|
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
|
||||||
@@ -28,11 +21,32 @@
|
|||||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="MinVersion">
|
||||||
|
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="MaxVersion">
|
||||||
|
<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_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="uTLS" v-if="inbound.tls" >
|
||||||
|
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px">
|
||||||
|
<a-select-option value=''>None</a-select-option>
|
||||||
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.server"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.server"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="alpn" placeholder="http/1.1,h2">
|
<a-form-item label="Alpn">
|
||||||
<a-input v-model.trim="inbound.stream.tls.alpn"></a-input>
|
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
|
||||||
|
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Allow insecure">
|
||||||
|
<a-switch v-model="inbound.stream.tls.settings[0].allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
||||||
@@ -42,18 +56,19 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.tls.certs[0].useFile">
|
<template v-if="inbound.stream.tls.certs[0].useFile">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-button @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
<a-input type="textarea" :rows="2" v-model="inbound.stream.tls.certs[0].cert"></a-input>
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
<a-input type="textarea" :rows="2" v-model="inbound.stream.tls.certs[0].key"></a-input>
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -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}}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:mask-closable="true"
|
:mask-closable="true"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="600px"
|
width="600px"
|
||||||
>
|
>
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr colspan="2">
|
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||||
<td v-if="inbound.tls">
|
<td v-if="inbound.tls">
|
||||||
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>
|
||||||
@@ -56,16 +57,27 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<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)">
|
||||||
@@ -84,14 +96,82 @@
|
|||||||
[[ 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>
|
|
||||||
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
|
||||||
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<table v-if="infoModal.clientSettings.subId + infoModal.clientSettings.tgId" style="margin-bottom: 10px;">
|
||||||
|
<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>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<th>{{ i18n "encryption" }}</th>
|
||||||
|
<th>{{ i18n "password" }}</th>
|
||||||
|
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||||
|
</tr><tr>
|
||||||
|
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||||
|
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||||
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<table v-if="inbound.protocol == Protocols.DOKODEMO" style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
|
||||||
|
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
||||||
|
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||||
|
<th>FollowRedirect</th>
|
||||||
|
</tr><tr>
|
||||||
|
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
||||||
|
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
||||||
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
|
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</table>
|
||||||
|
<table v-if="inbound.protocol == Protocols.SOCKS" style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<th>{{ i18n "password" }} Auth</th>
|
||||||
|
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||||
|
<th>IP</th>
|
||||||
|
</tr><tr>
|
||||||
|
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
||||||
|
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
||||||
|
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||||
|
</tr><tr v-if="inbound.settings.auth == 'password'">
|
||||||
|
<td> </td>
|
||||||
|
<td>{{ i18n "username" }}</td>
|
||||||
|
<td>{{ i18n "password" }}</td>
|
||||||
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
|
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||||
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</table>
|
||||||
|
<table v-if="inbound.protocol == Protocols.HTTP" style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<th> </th>
|
||||||
|
<th>{{ i18n "username" }}</th>
|
||||||
|
<th>{{ i18n "password" }}</th>
|
||||||
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
|
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||||
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
<div v-if="dbInbound.hasLink()">
|
<div v-if="dbInbound.hasLink()">
|
||||||
<a-divider>URL</a-divider>
|
<a-divider>URL</a-divider>
|
||||||
<p>[[ infoModal.link ]]</p>
|
<p>[[ infoModal.link ]]</p>
|
||||||
@@ -99,27 +179,28 @@
|
|||||||
</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(),
|
||||||
clientSettings: new Inbound.Settings(),
|
settings: null,
|
||||||
|
clientSettings: null,
|
||||||
clientStats: [],
|
clientStats: [],
|
||||||
upStats: 0,
|
upStats: 0,
|
||||||
downStats: 0,
|
downStats: 0,
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
link: null,
|
link: null,
|
||||||
index: 0,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
show(dbInbound, index=0) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.link = dbInbound.genLink(index);
|
this.link = dbInbound.genLink(index);
|
||||||
this.clientSettings = Object.values(JSON.parse(this.inbound.settings).clients)[index];
|
this.settings = JSON.parse(this.inbound.settings);
|
||||||
|
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
||||||
this.isExpired = this.inbound.isExpiry(index);
|
this.isExpired = this.inbound.isExpiry(index);
|
||||||
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email);
|
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
infoModalApp.$nextTick(() => {
|
infoModalApp.$nextTick(() => {
|
||||||
if (this.clipboard === null) {
|
if (this.clipboard === null) {
|
||||||
@@ -146,23 +227,26 @@
|
|||||||
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 true;
|
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: {
|
||||||
setQrCode(elmentId,index) {
|
|
||||||
content = infoModal.inbound.genLink(infoModal.dbInbound.address,infoModal.dbInbound.remark,index)
|
|
||||||
|
|
||||||
new QRious({
|
|
||||||
element: document.querySelector('#'+elmentId),
|
|
||||||
size: 260,
|
|
||||||
value: content,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
copyTextToClipboard(elmentId,content) {
|
copyTextToClipboard(elmentId,content) {
|
||||||
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||||
text: () => content,
|
text: () => content,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{{define "inboundModal"}}
|
{{define "inboundModal"}}
|
||||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
{{template "form/inbound"}}
|
{{template "form/inbound"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -95,6 +96,10 @@
|
|||||||
clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null
|
clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null
|
||||||
return clientStats ? clientStats['enable'] : true
|
return clientStats ? clientStats['enable'] : true
|
||||||
},
|
},
|
||||||
|
setDefaultCertData(){
|
||||||
|
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||||
|
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
||||||
|
},
|
||||||
getNewEmail(client) {
|
getNewEmail(client) {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||||
var string = '';
|
var string = '';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</a-tag>
|
</a-tag>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable style="margin-bottom: 20px;">
|
<a-card hoverable style="margin-bottom: 20px;" :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||||
@@ -41,17 +41,34 @@
|
|||||||
<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>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable>
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
<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"
|
||||||
@@ -63,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)">
|
<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" }}
|
||||||
@@ -74,21 +91,34 @@
|
|||||||
</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"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-menu-item key="showInfo">
|
||||||
|
<a-icon type="info-circle"></a-icon>
|
||||||
|
{{ i18n "info"}}
|
||||||
|
</a-menu-item>
|
||||||
|
</template>
|
||||||
<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"}}
|
||||||
@@ -98,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.isXTls" color="cyan">xtls</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>
|
||||||
@@ -108,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>
|
||||||
@@ -178,28 +228,28 @@
|
|||||||
}, {
|
}, {
|
||||||
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: 30,
|
width: 40,
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.protocol" }}',
|
||||||
|
align: 'left',
|
||||||
|
width: 70,
|
||||||
|
scopedSlots: { customRender: 'protocol' },
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "clients" }}',
|
||||||
|
align: 'left',
|
||||||
|
width: 50,
|
||||||
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 150,
|
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',
|
||||||
@@ -208,19 +258,21 @@
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
const innerColumns = [
|
const innerColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 60, 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: 80, 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' } },
|
||||||
{ title: 'UID', width: 150, dataIndex: "id" },
|
{ title: 'UID', width: 120, dataIndex: "id" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerTrojanColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 60, 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: 80, 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' } },
|
||||||
{ title: 'Password', width: 150, dataIndex: "password" },
|
{ title: 'Password', width: 120, dataIndex: "password" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
@@ -233,6 +285,11 @@
|
|||||||
dbInbounds: [],
|
dbInbounds: [],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
searchedInbounds: [],
|
searchedInbounds: [],
|
||||||
|
expireDiff: 0,
|
||||||
|
trafficDiff: 0,
|
||||||
|
defaultCert: '',
|
||||||
|
defaultKey: '',
|
||||||
|
clientCount: {},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning=true) {
|
loading(spinning=true) {
|
||||||
@@ -248,17 +305,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();
|
||||||
@@ -287,6 +393,9 @@
|
|||||||
case "qrcode":
|
case "qrcode":
|
||||||
this.showQrcode(dbInbound);
|
this.showQrcode(dbInbound);
|
||||||
break;
|
break;
|
||||||
|
case "showInfo":
|
||||||
|
this.showInfo(dbInbound);
|
||||||
|
break;
|
||||||
case "edit":
|
case "edit":
|
||||||
this.openEditInbound(dbInbound.id);
|
this.openEditInbound(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
@@ -302,6 +411,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;
|
||||||
@@ -336,6 +451,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 = {
|
||||||
@@ -350,9 +498,10 @@
|
|||||||
port: inbound.port,
|
port: inbound.port,
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
streamSettings: inbound.stream.toString(),
|
|
||||||
sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
|
|
||||||
};
|
};
|
||||||
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
|
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit('/xui/inbound/add', data, inModal);
|
await this.submit('/xui/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
async updateInbound(inbound, dbInbound) {
|
async updateInbound(inbound, dbInbound) {
|
||||||
@@ -368,9 +517,10 @@
|
|||||||
port: inbound.port,
|
port: inbound.port,
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
streamSettings: inbound.stream.toString(),
|
|
||||||
sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
|
|
||||||
};
|
};
|
||||||
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
|
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
||||||
},
|
},
|
||||||
openAddClient(dbInboundId) {
|
openAddClient(dbInboundId) {
|
||||||
@@ -426,7 +576,7 @@
|
|||||||
id: dbInbound.id,
|
id: dbInbound.id,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
await this.submit('/xui/inbound/addClient', data);
|
await this.submit('/xui/inbound/addClient/', data);
|
||||||
},
|
},
|
||||||
async updateClient(inbound, dbInbound, index) {
|
async updateClient(inbound, dbInbound, index) {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -440,6 +590,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
@@ -454,6 +605,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
||||||
@@ -473,6 +625,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/delClient/' + client.email, data),
|
onOk: () => this.submit('/xui/inbound/delClient/' + client.email, data),
|
||||||
@@ -497,6 +650,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) {
|
||||||
@@ -516,11 +679,32 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
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)
|
||||||
},
|
},
|
||||||
@@ -564,37 +748,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,28 +11,34 @@
|
|||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-card-dark h2 {
|
||||||
|
color: hsla(0,0%,100%,.65);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable>
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU</div>
|
<div>CPU</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:percent="status.mem.percent"></a-progress>
|
:percent="status.mem.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
@@ -45,6 +51,7 @@
|
|||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
@@ -53,6 +60,7 @@
|
|||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
:percent="status.disk.percent"></a-progress>
|
:percent="status.disk.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
@@ -67,23 +75,15 @@
|
|||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
x-ui: <a-tag color="green">{{ .cur_ver }}</a-tag>
|
||||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||||
<a-tooltip v-if="status.xray.state === State.Error">
|
|
||||||
<template slot="title">
|
|
||||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-tag color="green" @click="openSelectV2rayVersion">[[ status.xray.version ]]</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>
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
<a-tag color="#87d068">[[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.operationHoursDesc" }}
|
{{ i18n "pages.index.operationHoursDesc" }}
|
||||||
@@ -93,12 +93,35 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
|
{{ i18n "pages.index.xrayStatus" }}:
|
||||||
|
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||||
|
<a-tooltip v-if="status.xray.state === State.Error">
|
||||||
|
<template slot="title">
|
||||||
|
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||||
|
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||||
|
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
|
{{ i18n "menu.link" }}:
|
||||||
|
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Logs</a-tag>
|
||||||
|
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
|
||||||
|
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
tcp / udp {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
|
tcp / udp {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
@@ -109,7 +132,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
@@ -135,7 +158,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
@@ -166,7 +189,8 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
||||||
:closable="true" @ok="() => versionModal.visible = false"
|
:closable="true" @ok="() => versionModal.visible = false"
|
||||||
ok-text='{{ i18n "confirm" }}' cancel-text='{{ i18n "cancel"}}'>
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
|
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">
|
||||||
@@ -176,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 = {
|
||||||
@@ -269,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',
|
||||||
@@ -276,6 +345,7 @@
|
|||||||
siderDrawer,
|
siderDrawer,
|
||||||
status: new Status(),
|
status: new Status(),
|
||||||
versionModal,
|
versionModal,
|
||||||
|
logModal,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
},
|
},
|
||||||
@@ -307,6 +377,7 @@
|
|||||||
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
||||||
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
||||||
okText: '{{ i18n "confirm"}}',
|
okText: '{{ i18n "confirm"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
@@ -316,6 +387,45 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
//here add stop xray function
|
||||||
|
async stopXrayService() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('server/stopXrayService');
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//here add restart xray function
|
||||||
|
async restartXrayService() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('server/restartXrayService');
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-top-bar {
|
:not(.ant-card-dark)>.ant-tabs-top-bar {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
@@ -35,17 +35,19 @@
|
|||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-tabs default-active-key="1">
|
<a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
<a-tab-pane key="1" tab='{{ i18n "pages.setting.panelConfig"}}'>
|
<a-tab-pane key="1" tab='{{ i18n "pages.setting.panelConfig"}}'>
|
||||||
|
|
||||||
<a-list item-layout="horizontal" style="background: white">
|
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></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.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">
|
||||||
<a-list-item-meta title="Language"/>
|
<a-list-item-meta title="Language"/>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -56,9 +58,10 @@
|
|||||||
ref="selectLang"
|
ref="selectLang"
|
||||||
v-model="lang"
|
v-model="lang"
|
||||||
@change="setLang(lang)"
|
@change="setLang(lang)"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<a-select-option :value="l.value" label="China" v-for="l in supportLangs" >
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
@@ -71,7 +74,7 @@
|
|||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'>
|
<a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'>
|
||||||
<a-form style="background: white; padding: 20px">
|
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
|
||||||
<a-form-item label='{{ i18n "pages.setting.oldUsername"}}'>
|
<a-form-item label='{{ i18n "pages.setting.oldUsername"}}'>
|
||||||
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -93,7 +96,7 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
|
<a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
|
||||||
<a-list item-layout="horizontal" style="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.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
||||||
<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
|
<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
|
||||||
@@ -113,15 +116,17 @@
|
|||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'>
|
<a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'>
|
||||||
<a-list item-layout="horizontal" style="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="number" 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="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>
|
||||||
<a-tab-pane key="5" tab='{{ i18n "pages.setting.otherSetting"}}'>
|
<a-tab-pane key="5" tab='{{ i18n "pages.setting.otherSetting"}}'>
|
||||||
<a-list item-layout="horizontal" style="background: white">
|
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.timeZonee"}}' desc='{{ i18n "pages.setting.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.timeZonee"}}' desc='{{ i18n "pages.setting.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|||||||
30
web/job/check_cpu_usage.go
Normal file
30
web/job/check_cpu_usage.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckCpuJob struct {
|
||||||
|
tgbotService service.Tgbot
|
||||||
|
settingService service.SettingService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckCpuJob() *CheckCpuJob {
|
||||||
|
return new(CheckCpuJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here run is a interface method of Job interface
|
||||||
|
func (j *CheckCpuJob) Run() {
|
||||||
|
threshold, _ := j.settingService.GetTgCpu()
|
||||||
|
|
||||||
|
// get latest status of server
|
||||||
|
percent, err := cpu.Percent(1*time.Second, false)
|
||||||
|
if err == nil && percent[0] > float64(threshold) {
|
||||||
|
msg := fmt.Sprintf("🔴 CPU usage %.2f%% is more than threshold %d%%", percent[0], threshold)
|
||||||
|
j.tgbotService.SendMsgToTgbotAdmins(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/util/common"
|
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginStatus byte
|
type LoginStatus byte
|
||||||
@@ -20,229 +12,18 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StatsNotifyJob struct {
|
type StatsNotifyJob struct {
|
||||||
enable bool
|
xrayService service.XrayService
|
||||||
xrayService service.XrayService
|
tgbotService service.Tgbot
|
||||||
inboundService service.InboundService
|
|
||||||
settingService service.SettingService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStatsNotifyJob() *StatsNotifyJob {
|
func NewStatsNotifyJob() *StatsNotifyJob {
|
||||||
return new(StatsNotifyJob)
|
return new(StatsNotifyJob)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *StatsNotifyJob) SendMsgToTgbot(msg string) {
|
|
||||||
//Telegram bot basic info
|
|
||||||
tgBottoken, err := j.settingService.GetTgBotToken()
|
|
||||||
if err != nil || tgBottoken == "" {
|
|
||||||
logger.Warning("sendMsgToTgbot failed,GetTgBotToken fail:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tgBotid, err := j.settingService.GetTgBotChatId()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sendMsgToTgbot failed,GetTgBotChatId fail:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bot, err := tgbotapi.NewBotAPI(tgBottoken)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get tgbot error:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bot.Debug = true
|
|
||||||
fmt.Printf("Authorized on account %s", bot.Self.UserName)
|
|
||||||
info := tgbotapi.NewMessage(int64(tgBotid), msg)
|
|
||||||
//msg.ReplyToMessageID = int(tgBotid)
|
|
||||||
bot.Send(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here run is a interface method of Job interface
|
// Here run is a interface method of Job interface
|
||||||
func (j *StatsNotifyJob) Run() {
|
func (j *StatsNotifyJob) Run() {
|
||||||
if !j.xrayService.IsXrayRunning() {
|
if !j.xrayService.IsXrayRunning() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var info string
|
j.tgbotService.SendReport()
|
||||||
//get hostname
|
|
||||||
name, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get hostname error:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info = fmt.Sprintf("Hostname:%s\r\n", name)
|
|
||||||
//get ip address
|
|
||||||
var ip string
|
|
||||||
netInterfaces, err := net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("net.Interfaces failed, err:", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(netInterfaces); i++ {
|
|
||||||
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
|
||||||
addrs, _ := netInterfaces[i].Addrs()
|
|
||||||
|
|
||||||
for _, address := range addrs {
|
|
||||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
|
||||||
if ipnet.IP.To4() != nil {
|
|
||||||
ip = ipnet.IP.String()
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
ip = ipnet.IP.String()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info += fmt.Sprintf("IP:%s\r\n \r\n", ip)
|
|
||||||
|
|
||||||
// get traffic
|
|
||||||
inbouds, err := j.inboundService.GetAllInbounds()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("StatsNotifyJob run failed:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// NOTE:If there no any sessions here,need to notify here
|
|
||||||
// TODO:Sub-node push, automatic conversion format
|
|
||||||
for _, inbound := range inbouds {
|
|
||||||
info += fmt.Sprintf("Node name:%s\r\nPort:%d\r\nUpload↑:%s\r\nDownload↓:%s\r\nTotal:%s\r\n", inbound.Remark, inbound.Port, common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down), common.FormatTraffic((inbound.Up + inbound.Down)))
|
|
||||||
if inbound.ExpiryTime == 0 {
|
|
||||||
info += fmt.Sprintf("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"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j.SendMsgToTgbot(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *StatsNotifyJob) UserLoginNotify(username string, ip string, time string, status LoginStatus) {
|
|
||||||
if username == "" || ip == "" || time == "" {
|
|
||||||
logger.Warning("UserLoginNotify failed,invalid info")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var msg string
|
|
||||||
// Get hostname
|
|
||||||
name, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get hostname error:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if status == LoginSuccess {
|
|
||||||
msg = fmt.Sprintf("Successfully logged-in to the panel\r\nHostname:%s\r\n", name)
|
|
||||||
} else if status == LoginFail {
|
|
||||||
msg = fmt.Sprintf("Login to the panel was unsuccessful\r\nHostname:%s\r\n", name)
|
|
||||||
}
|
|
||||||
msg += fmt.Sprintf("Time:%s\r\n", time)
|
|
||||||
msg += fmt.Sprintf("Username:%s\r\n", username)
|
|
||||||
msg += fmt.Sprintf("IP:%s\r\n", ip)
|
|
||||||
j.SendMsgToTgbot(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
var numericKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
|
||||||
tgbotapi.NewInlineKeyboardRow(
|
|
||||||
tgbotapi.NewInlineKeyboardButtonData("Get Usage", "get_usage"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
func (j *StatsNotifyJob) OnReceive() *StatsNotifyJob {
|
|
||||||
tgBottoken, err := j.settingService.GetTgBotToken()
|
|
||||||
if err != nil || tgBottoken == "" {
|
|
||||||
logger.Warning("sendMsgToTgbot failed,GetTgBotToken fail:", err)
|
|
||||||
return j
|
|
||||||
}
|
|
||||||
bot, err := tgbotapi.NewBotAPI(tgBottoken)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get tgbot error:", err)
|
|
||||||
return j
|
|
||||||
}
|
|
||||||
bot.Debug = false
|
|
||||||
u := tgbotapi.NewUpdate(0)
|
|
||||||
u.Timeout = 10
|
|
||||||
|
|
||||||
updates := bot.GetUpdatesChan(u)
|
|
||||||
|
|
||||||
for update := range updates {
|
|
||||||
if update.Message == nil {
|
|
||||||
|
|
||||||
if update.CallbackQuery != nil {
|
|
||||||
// Respond to the callback query, telling Telegram to show the user
|
|
||||||
// a message with the data received.
|
|
||||||
callback := tgbotapi.NewCallback(update.CallbackQuery.ID, update.CallbackQuery.Data)
|
|
||||||
if _, err := bot.Request(callback); err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// And finally, send a message containing the data received.
|
|
||||||
msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, "")
|
|
||||||
|
|
||||||
switch update.CallbackQuery.Data {
|
|
||||||
case "get_usage":
|
|
||||||
msg.Text = "for get your usage send command like this : \n <code>/usage uuid | id</code> \n example : <code>/usage fc3239ed-8f3b-4151-ff51-b183d5182142</code>"
|
|
||||||
msg.ParseMode = "HTML"
|
|
||||||
}
|
|
||||||
if _, err := bot.Send(msg); err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !update.Message.IsCommand() { // ignore any non-command Messages
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new MessageConfig. We don't have text yet,
|
|
||||||
// so we leave it empty.
|
|
||||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "")
|
|
||||||
|
|
||||||
// Extract the command from the Message.
|
|
||||||
switch update.Message.Command() {
|
|
||||||
case "help":
|
|
||||||
msg.Text = "What you need?"
|
|
||||||
msg.ReplyMarkup = numericKeyboard
|
|
||||||
case "start":
|
|
||||||
msg.Text = "Hi :) \n What you need?"
|
|
||||||
msg.ReplyMarkup = numericKeyboard
|
|
||||||
|
|
||||||
case "status":
|
|
||||||
msg.Text = "bot is ok."
|
|
||||||
|
|
||||||
case "usage":
|
|
||||||
msg.Text = j.getClientUsage(update.Message.CommandArguments())
|
|
||||||
default:
|
|
||||||
msg.Text = "I don't know that command, /help"
|
|
||||||
msg.ReplyMarkup = numericKeyboard
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := bot.Send(msg); err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return j
|
|
||||||
|
|
||||||
}
|
|
||||||
func (j *StatsNotifyJob) getClientUsage(id string) string {
|
|
||||||
traffic, err := j.inboundService.GetClientTrafficById(id)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
return "something wrong!"
|
|
||||||
}
|
|
||||||
expiryTime := ""
|
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = fmt.Sprintf("unlimited")
|
|
||||||
} else {
|
|
||||||
expiryTime = fmt.Sprintf("%s", time.Unix((traffic.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
|
||||||
}
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = fmt.Sprintf("unlimited")
|
|
||||||
} else {
|
|
||||||
total = fmt.Sprintf("%s", 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)
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
"loglevel": "warning"
|
||||||
"access": "./access.log"
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"api": {
|
"api": {
|
||||||
|
|||||||
@@ -319,12 +319,18 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
|||||||
|
|
||||||
if len(clients[index].Email) > 0 {
|
if len(clients[index].Email) > 0 {
|
||||||
if len(oldClients[index].Email) > 0 {
|
if len(oldClients[index].Email) > 0 {
|
||||||
s.UpdateClientStat(oldClients[index].Email, &clients[index])
|
err = s.UpdateClientStat(oldClients[index].Email, &clients[index])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
s.AddClientStat(inbound.Id, &clients[index])
|
s.AddClientStat(inbound.Id, &clients[index])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.DelClientStat(db, oldClients[index].Email)
|
err = s.DelClientStat(db, oldClients[index].Email)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
@@ -360,11 +366,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()
|
||||||
@@ -372,7 +383,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()
|
||||||
@@ -383,50 +407,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))
|
||||||
|
}
|
||||||
if err != nil {
|
|
||||||
logger.Warning("AddClientTraffic update data ", err)
|
settings["clients"] = clientsInterface
|
||||||
continue
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
full_traffics = append(full_traffics, client_traffic)
|
||||||
}
|
}
|
||||||
return
|
return full_traffics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||||
@@ -449,6 +489,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()
|
||||||
|
|
||||||
@@ -501,12 +552,87 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (s *InboundService) GetClientTrafficById(uuid string) (traffic *xray.ClientTraffic, err 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 == gorm.ErrRecordNotFound {
|
||||||
|
logger.Warning(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return traffics, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic []*xray.ClientTraffic, err error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var traffics []*xray.ClientTraffic
|
||||||
|
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where("email like ?", "%"+email+"%").Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
logger.Warning(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return traffics, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
traffic = &xray.ClientTraffic{}
|
traffic = &xray.ClientTraffic{}
|
||||||
|
|
||||||
err = db.Model(model.Inbound{}).Where("settings like ?", "%"+uuid+"%").First(inbound).Error
|
err = db.Model(model.Inbound{}).Where("settings like ?", "%\""+query+"\"%").First(inbound).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
@@ -520,9 +646,17 @@ func (s *InboundService) GetClientTrafficById(uuid string) (traffic *xray.Client
|
|||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
clients := settings["clients"]
|
clients := settings["clients"]
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if uuid == client.ID {
|
if client.ID == query && client.Email != "" {
|
||||||
traffic.Email = client.Email
|
traffic.Email = client.Email
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
if client.Password == query && client.Email != "" {
|
||||||
|
traffic.Email = client.Email
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if traffic.Email == "" {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error
|
err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -531,3 +665,13 @@ func (s *InboundService) GetClientTrafficById(uuid 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"
|
||||||
@@ -198,6 +201,30 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
|||||||
return versions, nil
|
return versions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) StopXrayService() (string error) {
|
||||||
|
|
||||||
|
err := s.xrayService.StopXray()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("stop xray failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) RestartXrayService() (string error) {
|
||||||
|
|
||||||
|
s.xrayService.StopXray()
|
||||||
|
defer func() {
|
||||||
|
err := s.xrayService.RestartXray(true)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("start xray failed:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServerService) downloadXRay(version string) (string, error) {
|
func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||||
osName := runtime.GOOS
|
osName := runtime.GOOS
|
||||||
arch := runtime.GOARCH
|
arch := runtime.GOARCH
|
||||||
@@ -300,3 +327,66 @@ 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,11 +28,15 @@ 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": "0",
|
"tgBotChatId": "",
|
||||||
"tgRunTime": "",
|
"tgRunTime": "@daily",
|
||||||
|
"tgBotBackup": "false",
|
||||||
|
"tgCpu": "0",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -202,30 +206,46 @@ func (s *SettingService) SetTgBotToken(token string) error {
|
|||||||
return s.setString("tgBotToken", token)
|
return s.setString("tgBotToken", token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTgBotChatId() (int, error) {
|
func (s *SettingService) GetTgBotChatId() (string, error) {
|
||||||
return s.getInt("tgBotChatId")
|
return s.getString("tgBotChatId")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetTgBotChatId(chatId int) error {
|
func (s *SettingService) SetTgBotChatId(chatIds string) error {
|
||||||
return s.setInt("tgBotChatId", chatId)
|
return s.setString("tgBotChatId", chatIds)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) SetTgbotenabled(value bool) error {
|
|
||||||
return s.setBool("tgBotEnable", value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTgbotenabled() (bool, error) {
|
func (s *SettingService) GetTgbotenabled() (bool, error) {
|
||||||
return s.getBool("tgBotEnable")
|
return s.getBool("tgBotEnable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetTgbotRuntime(time string) error {
|
func (s *SettingService) SetTgbotenabled(value bool) error {
|
||||||
return s.setString("tgRunTime", time)
|
return s.setBool("tgBotEnable", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTgbotRuntime() (string, error) {
|
func (s *SettingService) GetTgbotRuntime() (string, error) {
|
||||||
return s.getString("tgRunTime")
|
return s.getString("tgRunTime")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetTgbotRuntime(time string) error {
|
||||||
|
return s.setString("tgRunTime", time)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgBotBackup() (bool, error) {
|
||||||
|
return s.getBool("tgBotBackup")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetTgBotBackup(value bool) error {
|
||||||
|
return s.setBool("tgBotBackup", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgCpu() (int, error) {
|
||||||
|
return s.getInt("tgCpu")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetTgCpu(value int) error {
|
||||||
|
return s.setInt("tgCpu", value)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetPort() (int, error) {
|
func (s *SettingService) GetPort() (int, error) {
|
||||||
return s.getInt("webPort")
|
return s.getInt("webPort")
|
||||||
}
|
}
|
||||||
@@ -242,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 ""
|
||||||
|
}
|
||||||
612
web/service/tgbot.go
Normal file
612
web/service/tgbot.go
Normal file
@@ -0,0 +1,612 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"x-ui/config"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/xray"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bot *tgbotapi.BotAPI
|
||||||
|
var adminIds []int64
|
||||||
|
var isRunning bool
|
||||||
|
|
||||||
|
type LoginStatus byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
LoginSuccess LoginStatus = 1
|
||||||
|
LoginFail LoginStatus = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tgbot struct {
|
||||||
|
inboundService InboundService
|
||||||
|
settingService SettingService
|
||||||
|
serverService ServerService
|
||||||
|
lastStatus *Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) NewTgbot() *Tgbot {
|
||||||
|
return new(Tgbot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) Start() error {
|
||||||
|
tgBottoken, err := t.settingService.GetTgBotToken()
|
||||||
|
if err != nil || tgBottoken == "" {
|
||||||
|
logger.Warning("Get TgBotToken failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tgBotid, err := t.settingService.GetTgBotChatId()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Get GetTgBotChatId failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, adminId := range strings.Split(tgBotid, ",") {
|
||||||
|
id, err := strconv.Atoi(adminId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to get IDs from GetTgBotChatId:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
adminIds = append(adminIds, int64(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
bot, err = tgbotapi.NewBotAPI(tgBottoken)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Get tgbot's api error:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bot.Debug = false
|
||||||
|
|
||||||
|
// listen for TG bot income messages
|
||||||
|
if !isRunning {
|
||||||
|
logger.Info("Starting Telegram receiver ...")
|
||||||
|
go t.OnReceive()
|
||||||
|
isRunning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) IsRunnging() bool {
|
||||||
|
return isRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) Stop() {
|
||||||
|
bot.StopReceivingUpdates()
|
||||||
|
logger.Info("Stop Telegram receiver ...")
|
||||||
|
isRunning = false
|
||||||
|
adminIds = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) OnReceive() {
|
||||||
|
u := tgbotapi.NewUpdate(0)
|
||||||
|
u.Timeout = 10
|
||||||
|
|
||||||
|
updates := bot.GetUpdatesChan(u)
|
||||||
|
|
||||||
|
for update := range updates {
|
||||||
|
tgId := update.FromChat().ID
|
||||||
|
chatId := update.FromChat().ChatConfig().ChatID
|
||||||
|
isAdmin := checkAdmin(tgId)
|
||||||
|
if update.Message == nil {
|
||||||
|
if update.CallbackQuery != nil {
|
||||||
|
t.asnwerCallback(update.CallbackQuery, isAdmin)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if update.Message.IsCommand() {
|
||||||
|
t.answerCommand(update.Message, chatId, isAdmin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin bool) {
|
||||||
|
msg := ""
|
||||||
|
// Extract the command from the Message.
|
||||||
|
switch message.Command() {
|
||||||
|
case "help":
|
||||||
|
msg = "This bot is providing you some specefic data from the server.\n\n Please choose:"
|
||||||
|
case "start":
|
||||||
|
msg = "Hello <i>" + message.From.FirstName + "</i> 👋"
|
||||||
|
if isAdmin {
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
msg += "\nWelcome to <b>" + hostname + "</b> management bot"
|
||||||
|
}
|
||||||
|
msg += "\n\nI can do some magics for you, please choose:"
|
||||||
|
case "status":
|
||||||
|
msg = "bot is ok ✅"
|
||||||
|
case "usage":
|
||||||
|
if len(message.CommandArguments()) > 1 {
|
||||||
|
if isAdmin {
|
||||||
|
t.searchClient(chatId, message.CommandArguments())
|
||||||
|
} else {
|
||||||
|
t.searchForClient(chatId, message.CommandArguments())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg = "❗Please provide a text for search!"
|
||||||
|
}
|
||||||
|
case "inbound":
|
||||||
|
if isAdmin {
|
||||||
|
t.searchInbound(chatId, message.CommandArguments())
|
||||||
|
} else {
|
||||||
|
msg = "❗ Unknown command"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
msg = "❗ Unknown command"
|
||||||
|
}
|
||||||
|
t.SendAnswer(chatId, msg, isAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
|
||||||
|
// Respond to the callback query, telling Telegram to show the user
|
||||||
|
// a message with the data received.
|
||||||
|
callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data)
|
||||||
|
if _, err := bot.Request(callback); err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch callbackQuery.Data {
|
||||||
|
case "get_usage":
|
||||||
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.getServerUsage())
|
||||||
|
case "inbounds":
|
||||||
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.getInboundUsages())
|
||||||
|
case "deplete_soon":
|
||||||
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.getExhausted())
|
||||||
|
case "get_backup":
|
||||||
|
t.sendBackup(callbackQuery.From.ID)
|
||||||
|
case "client_traffic":
|
||||||
|
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
||||||
|
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/vless and Password for Trojan.")
|
||||||
|
case "commands":
|
||||||
|
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>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAdmin(tgId int64) bool {
|
||||||
|
for _, adminId := range adminIds {
|
||||||
|
if adminId == tgId {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
||||||
|
var numericKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("Server Usage", "get_usage"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("Get DB Backup", "get_backup"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("Get Inbounds", "inbounds"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("Deplete soon", "deplete_soon"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("Commands", "commands"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var numericKeyboardClient = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("Get Usage", "client_traffic"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("Commands", "client_commands"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
msgConfig := tgbotapi.NewMessage(chatId, msg)
|
||||||
|
msgConfig.ParseMode = "HTML"
|
||||||
|
if isAdmin {
|
||||||
|
msgConfig.ReplyMarkup = numericKeyboard
|
||||||
|
} else {
|
||||||
|
msgConfig.ReplyMarkup = numericKeyboardClient
|
||||||
|
}
|
||||||
|
_, err := bot.Send(msgConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Error sending telegram message :", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) SendMsgToTgbot(tgid int64, msg string) {
|
||||||
|
var allMessages []string
|
||||||
|
limit := 2000
|
||||||
|
// paging message if it is big
|
||||||
|
if len(msg) > limit {
|
||||||
|
messages := strings.Split(msg, "\r\n \r\n")
|
||||||
|
lastIndex := -1
|
||||||
|
for _, message := range messages {
|
||||||
|
if (len(allMessages) == 0) || (len(allMessages[lastIndex])+len(message) > limit) {
|
||||||
|
allMessages = append(allMessages, message)
|
||||||
|
lastIndex++
|
||||||
|
} else {
|
||||||
|
allMessages[lastIndex] += "\r\n \r\n" + message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allMessages = append(allMessages, msg)
|
||||||
|
}
|
||||||
|
for _, message := range allMessages {
|
||||||
|
info := tgbotapi.NewMessage(tgid, message)
|
||||||
|
info.ParseMode = "HTML"
|
||||||
|
_, err := bot.Send(info)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Error sending telegram message :", err)
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) SendMsgToTgbotAdmins(msg string) {
|
||||||
|
for _, adminId := range adminIds {
|
||||||
|
t.SendMsgToTgbot(adminId, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) SendReport() {
|
||||||
|
runTime, err := t.settingService.GetTgbotRuntime()
|
||||||
|
if err == nil && len(runTime) > 0 {
|
||||||
|
t.SendMsgToTgbotAdmins("🕰 Scheduled reports: " + runTime + "\r\nDate-Time: " + time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
info := t.getServerUsage()
|
||||||
|
t.SendMsgToTgbotAdmins(info)
|
||||||
|
exhausted := t.getExhausted()
|
||||||
|
t.SendMsgToTgbotAdmins(exhausted)
|
||||||
|
backupEnable, err := t.settingService.GetTgBotBackup()
|
||||||
|
if err == nil && backupEnable {
|
||||||
|
for _, adminId := range adminIds {
|
||||||
|
t.sendBackup(int64(adminId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) getServerUsage() string {
|
||||||
|
var info string
|
||||||
|
//get hostname
|
||||||
|
name, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("get hostname error:", err)
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
info = fmt.Sprintf("💻 Hostname: %s\r\n", name)
|
||||||
|
info += fmt.Sprintf("🚀X-UI Version: %s\r\n", config.GetVersion())
|
||||||
|
//get ip address
|
||||||
|
var ip string
|
||||||
|
var ipv6 string
|
||||||
|
netInterfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("net.Interfaces failed, err:", err.Error())
|
||||||
|
info += "🌐 IP: Unknown\r\n \r\n"
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len(netInterfaces); i++ {
|
||||||
|
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
||||||
|
addrs, _ := netInterfaces[i].Addrs()
|
||||||
|
|
||||||
|
for _, address := range addrs {
|
||||||
|
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||||
|
if ipnet.IP.To4() != nil {
|
||||||
|
ip += ipnet.IP.String() + " "
|
||||||
|
} else if ipnet.IP.To16() != nil && !ipnet.IP.IsLinkLocalUnicast() {
|
||||||
|
ipv6 += ipnet.IP.String() + " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info += fmt.Sprintf("🌐IP: %s\r\n🌐IPv6: %s\r\n", ip, ipv6)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get latest status of server
|
||||||
|
t.lastStatus = t.serverService.GetStatus(t.lastStatus)
|
||||||
|
info += fmt.Sprintf("🔌Server Uptime: %d days\r\n", int(t.lastStatus.Uptime/86400))
|
||||||
|
info += fmt.Sprintf("📈Server Load: %.1f, %.1f, %.1f\r\n", t.lastStatus.Loads[0], t.lastStatus.Loads[1], t.lastStatus.Loads[2])
|
||||||
|
info += fmt.Sprintf("📋Server Memory: %s/%s\r\n", common.FormatTraffic(int64(t.lastStatus.Mem.Current)), common.FormatTraffic(int64(t.lastStatus.Mem.Total)))
|
||||||
|
info += fmt.Sprintf("🔹TcpCount: %d\r\n", t.lastStatus.TcpCount)
|
||||||
|
info += fmt.Sprintf("🔸UdpCount: %d\r\n", t.lastStatus.UdpCount)
|
||||||
|
info += fmt.Sprintf("🚦Traffic: %s (↑%s,↓%s)\r\n", common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent+t.lastStatus.NetTraffic.Recv)), common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent)), common.FormatTraffic(int64(t.lastStatus.NetTraffic.Recv)))
|
||||||
|
info += fmt.Sprintf("ℹXray status: %s", t.lastStatus.Xray.State)
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status LoginStatus) {
|
||||||
|
if username == "" || ip == "" || time == "" {
|
||||||
|
logger.Warning("UserLoginNotify failed,invalid info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var msg string
|
||||||
|
// Get hostname
|
||||||
|
name, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("get hostname error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if status == LoginSuccess {
|
||||||
|
msg = fmt.Sprintf("✅ Successfully logged-in to the panel\r\nHostname:%s\r\n", name)
|
||||||
|
} else if status == LoginFail {
|
||||||
|
msg = fmt.Sprintf("❗ Login to the panel was unsuccessful\r\nHostname:%s\r\n", name)
|
||||||
|
}
|
||||||
|
msg += fmt.Sprintf("⏰ Time:%s\r\n", time)
|
||||||
|
msg += fmt.Sprintf("🆔 Username:%s\r\n", username)
|
||||||
|
msg += fmt.Sprintf("🌐 IP:%s\r\n", ip)
|
||||||
|
t.SendMsgToTgbotAdmins(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) getInboundUsages() string {
|
||||||
|
info := ""
|
||||||
|
// get traffic
|
||||||
|
inbouds, err := t.inboundService.GetAllInbounds()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("GetAllInbounds run failed:", err)
|
||||||
|
info += "❌ Failed to get inbounds"
|
||||||
|
} else {
|
||||||
|
// NOTE:If there no any sessions here,need to notify here
|
||||||
|
// TODO:Sub-node push, automatic conversion format
|
||||||
|
for _, inbound := range inbouds {
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
msg := "❌ Something went wrong!"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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>"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
t.SendAnswer(chatId, "Please choose:", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) searchClient(chatId int64, email string) {
|
||||||
|
traffics, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
msg := "❌ Something went wrong!"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(traffics) == 0 {
|
||||||
|
msg := "No result!"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
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) 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) {
|
||||||
|
traffic, err := t.inboundService.SearchClientTraffic(query)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
msg := "❌ Something went wrong!"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if traffic == nil {
|
||||||
|
msg := "No result!"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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) getExhausted() string {
|
||||||
|
trDiff := int64(0)
|
||||||
|
exDiff := int64(0)
|
||||||
|
now := time.Now().Unix() * 1000
|
||||||
|
var exhaustedInbounds []model.Inbound
|
||||||
|
var exhaustedClients []xray.ClientTraffic
|
||||||
|
var disabledInbounds []model.Inbound
|
||||||
|
var disabledClients []xray.ClientTraffic
|
||||||
|
output := ""
|
||||||
|
TrafficThreshold, err := t.settingService.GetTrafficDiff()
|
||||||
|
if err == nil && TrafficThreshold > 0 {
|
||||||
|
trDiff = int64(TrafficThreshold) * 1073741824
|
||||||
|
}
|
||||||
|
ExpireThreshold, err := t.settingService.GetExpireDiff()
|
||||||
|
if err == nil && ExpireThreshold > 0 {
|
||||||
|
exDiff = int64(ExpireThreshold) * 86400000
|
||||||
|
}
|
||||||
|
inbounds, err := t.inboundService.GetAllInbounds()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Unable to load Inbounds", err)
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
if inbound.Enable {
|
||||||
|
if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) ||
|
||||||
|
(inbound.Total > 0 && (inbound.Total-(inbound.Up+inbound.Down) < trDiff)) {
|
||||||
|
exhaustedInbounds = append(exhaustedInbounds, *inbound)
|
||||||
|
}
|
||||||
|
if len(inbound.ClientStats) > 0 {
|
||||||
|
for _, client := range inbound.ClientStats {
|
||||||
|
if client.Enable {
|
||||||
|
if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) ||
|
||||||
|
(client.Total > 0 && (client.Total-(client.Up+client.Down) < trDiff)) {
|
||||||
|
exhaustedClients = append(exhaustedClients, client)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disabledClients = append(disabledClients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disabledInbounds = append(disabledInbounds, *inbound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(exhaustedInbounds) > 0 {
|
||||||
|
output += "Exhausted Inbounds:\r\n"
|
||||||
|
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))
|
||||||
|
if inbound.ExpiryTime == 0 {
|
||||||
|
output += "Expire date: ♾Unlimited\r\n \r\n"
|
||||||
|
} else {
|
||||||
|
output += fmt.Sprintf("Expire date:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(exhaustedClients) > 0 {
|
||||||
|
output += "Exhausted Clients:\r\n"
|
||||||
|
for _, traffic := range exhaustedClients {
|
||||||
|
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 date: %s\r\n \r\n",
|
||||||
|
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
||||||
|
total, expiryTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) sendBackup(chatId int64) {
|
||||||
|
sendingTime := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
t.SendMsgToTgbot(chatId, "Backup time: "+sendingTime)
|
||||||
|
file := tgbotapi.FilePath(config.GetDBPath())
|
||||||
|
msg := tgbotapi.NewDocument(chatId, file)
|
||||||
|
_, err := bot.Send(msg)
|
||||||
|
if err != nil {
|
||||||
|
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,8 +33,11 @@
|
|||||||
"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"
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
@@ -69,6 +72,8 @@
|
|||||||
"memory" = "Memory"
|
"memory" = "Memory"
|
||||||
"hard" = "Hard disk"
|
"hard" = "Hard disk"
|
||||||
"xrayStatus" = "xray Status"
|
"xrayStatus" = "xray Status"
|
||||||
|
"stopXray" = "Stop"
|
||||||
|
"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"
|
||||||
@@ -126,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"
|
||||||
@@ -135,6 +152,14 @@
|
|||||||
"submitEdit" = "Save changes"
|
"submitEdit" = "Save changes"
|
||||||
"clientCount" = "Number of clients"
|
"clientCount" = "Number of clients"
|
||||||
"bulk" = "Add bulk"
|
"bulk" = "Add bulk"
|
||||||
|
"method" = "Method"
|
||||||
|
"first" = "First"
|
||||||
|
"last" = "Last"
|
||||||
|
"prefix" = "Prefix"
|
||||||
|
"postfix" = "postfix"
|
||||||
|
"delayedStart" = "Start after first use"
|
||||||
|
"expireDays" = "Expire days"
|
||||||
|
"days" = "day(s)"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Obtain"
|
"obtain" = "Obtain"
|
||||||
@@ -172,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"
|
||||||
@@ -198,10 +223,18 @@
|
|||||||
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
||||||
"telegramToken" = "Telegram Token"
|
"telegramToken" = "Telegram Token"
|
||||||
"telegramTokenDesc" = "Restart the panel to take effect"
|
"telegramTokenDesc" = "Restart the panel to take effect"
|
||||||
"telegramChatId" = "Telegram ChatId"
|
"telegramChatId" = "Telegram Admin ChatIds"
|
||||||
"telegramChatIdDesc" = "Restart the panel to take effect"
|
"telegramChatIdDesc" = "Multi chatIDs separated by comma. Restart the panel to take effect"
|
||||||
"telegramNotifyTime" = "Telegram bot notification time"
|
"telegramNotifyTime" = "Telegram bot notification time"
|
||||||
"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"
|
||||||
|
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
||||||
|
"expireTimeDiff" = "Exhaustion time threshold"
|
||||||
|
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
||||||
|
"trafficDiff" = "Exhaustion traffic threshold"
|
||||||
|
"trafficDiffDesc" = "Detect exhaustion before finishing traffic (unit:GB)"
|
||||||
|
"tgNotifyCpu" = "CPU percentage alert threshold"
|
||||||
|
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
|
||||||
"timeZonee" = "Time Zone"
|
"timeZonee" = "Time Zone"
|
||||||
"timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
|
"timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
|
||||||
|
|
||||||
|
|||||||
@@ -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,12 +72,14 @@
|
|||||||
"memory" = "حافظه رم"
|
"memory" = "حافظه رم"
|
||||||
"hard" = "حافظه دیسک"
|
"hard" = "حافظه دیسک"
|
||||||
"xrayStatus" = "وضعیت Xray"
|
"xrayStatus" = "وضعیت Xray"
|
||||||
|
"stopXray" = "توقف"
|
||||||
|
"restartXray" = "شروع مجدد"
|
||||||
"xraySwitch" = "تغییر ورژن"
|
"xraySwitch" = "تغییر ورژن"
|
||||||
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
||||||
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
||||||
"operationHours" = "ساعت فعال"
|
"operationHours" = "مدت فعالیت"
|
||||||
"operationHoursDesc" = "ساعت فعال بعد از شروع سیستم"
|
"operationHoursDesc" = "مدت فعالیت سیستم بعد از روشن شدن"
|
||||||
"systemLoad" = "سرعت لود سیستم"
|
"systemLoad" = "بار روی سیستم"
|
||||||
"connectionCount" = "تعداد کانکشن ها"
|
"connectionCount" = "تعداد کانکشن ها"
|
||||||
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"
|
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"
|
||||||
"upSpeed" = "سرعت آپلود در حال حاضر سیستم"
|
"upSpeed" = "سرعت آپلود در حال حاضر سیستم"
|
||||||
@@ -120,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" = "کاربر جدید"
|
||||||
@@ -135,6 +152,14 @@
|
|||||||
"submitEdit" = "ذخیره تغییرات"
|
"submitEdit" = "ذخیره تغییرات"
|
||||||
"clientCount" = "تعداد کاربران"
|
"clientCount" = "تعداد کاربران"
|
||||||
"bulk" = "انبوه سازی"
|
"bulk" = "انبوه سازی"
|
||||||
|
"method" = "روش"
|
||||||
|
"first" = "از"
|
||||||
|
"last" = "تا"
|
||||||
|
"prefix" = "پیشوند"
|
||||||
|
"postfix" = "پسوند"
|
||||||
|
"delayedStart" = "شروع بعد از اولین استفاده"
|
||||||
|
"expireDays" = "روزهای اعتبار"
|
||||||
|
"days" = "(روز)"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Obtain"
|
"obtain" = "Obtain"
|
||||||
@@ -170,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" = "باید با '/' شروع شود و با '/' تمام شود. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
@@ -198,10 +223,18 @@
|
|||||||
"telegramBotEnableDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramBotEnableDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"telegramToken" = "توکن تلگرام"
|
"telegramToken" = "توکن تلگرام"
|
||||||
"telegramTokenDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramTokenDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"telegramChatId" = "آی دی تلگرام مدیریت . از ربات @getidsbot آی دی خود را دریافت کنید"
|
"telegramChatId" = "آی دی تلگرام مدیریت"
|
||||||
"telegramChatIdDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramChatIdDesc" = "با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
||||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی Crontab استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||||
|
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||||
|
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
||||||
|
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
||||||
|
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
||||||
|
"trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)"
|
||||||
|
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
|
||||||
|
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
||||||
"timeZonee" = "منظقه زمانی"
|
"timeZonee" = "منظقه زمانی"
|
||||||
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,11 @@
|
|||||||
"host" = "主持人"
|
"host" = "主持人"
|
||||||
"path" = "小路"
|
"path" = "小路"
|
||||||
"camouflage" = "伪装"
|
"camouflage" = "伪装"
|
||||||
|
"status" = "状态"
|
||||||
"enabled" = "开启"
|
"enabled" = "开启"
|
||||||
"disabled" = "关闭"
|
"disabled" = "关闭"
|
||||||
|
"depleted" = "耗尽"
|
||||||
|
"depletingSoon" = "即将耗尽"
|
||||||
"domainName" = "域名"
|
"domainName" = "域名"
|
||||||
"additional" = "额外"
|
"additional" = "额外"
|
||||||
"monitor" = "监听"
|
"monitor" = "监听"
|
||||||
@@ -69,6 +72,8 @@
|
|||||||
"memory" = "内存"
|
"memory" = "内存"
|
||||||
"hard" = "硬盘"
|
"hard" = "硬盘"
|
||||||
"xrayStatus" = "xray 状态"
|
"xrayStatus" = "xray 状态"
|
||||||
|
"stopXray" = "停止"
|
||||||
|
"restartXray" = "重启"
|
||||||
"xraySwitch" = "切换版本"
|
"xraySwitch" = "切换版本"
|
||||||
"xraySwitchClick" = "点击你想切换的版本"
|
"xraySwitchClick" = "点击你想切换的版本"
|
||||||
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
|
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
|
||||||
@@ -127,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" = "添加客户端"
|
||||||
@@ -135,6 +152,14 @@
|
|||||||
"submitEdit" = "保存修改"
|
"submitEdit" = "保存修改"
|
||||||
"clientCount" = "客户数量"
|
"clientCount" = "客户数量"
|
||||||
"bulk" = "批量创建"
|
"bulk" = "批量创建"
|
||||||
|
"method" = "方法"
|
||||||
|
"first" = "第一"
|
||||||
|
"last" = "最后"
|
||||||
|
"prefix" = "前缀"
|
||||||
|
"postfix" = "后缀"
|
||||||
|
"delayedStart" = "首次使用后开始"
|
||||||
|
"expireDays" = "过期天数"
|
||||||
|
"days" = "天"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "获取"
|
"obtain" = "获取"
|
||||||
@@ -198,10 +223,18 @@
|
|||||||
"telegramBotEnableDesc" = "重启面板生效"
|
"telegramBotEnableDesc" = "重启面板生效"
|
||||||
"telegramToken" = "电报机器人TOKEN"
|
"telegramToken" = "电报机器人TOKEN"
|
||||||
"telegramTokenDesc" = "重启面板生效"
|
"telegramTokenDesc" = "重启面板生效"
|
||||||
"telegramChatId" = "电报机器人ChatId"
|
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效"
|
||||||
"telegramChatIdDesc" = "重启面板生效"
|
"telegramChatIdDesc" = "重启面板生效"
|
||||||
"telegramNotifyTime" = "电报机器人通知时间"
|
"telegramNotifyTime" = "电报机器人通知时间"
|
||||||
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
||||||
|
"tgNotifyBackup" = "数据库备份"
|
||||||
|
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
||||||
|
"expireTimeDiff" = "耗尽时间阈值"
|
||||||
|
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
||||||
|
"trafficDiff" = "耗尽流量阈值"
|
||||||
|
"trafficDiffDesc" = "完成流量前检测耗尽(单位:GB)"
|
||||||
|
"tgNotifyCpu" = "CPU 百分比警报阈值"
|
||||||
|
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
||||||
"timeZonee" = "时区"
|
"timeZonee" = "时区"
|
||||||
"timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效"
|
"timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效"
|
||||||
|
|
||||||
|
|||||||
30
web/web.go
30
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,10 +88,11 @@ 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
|
||||||
inboundService service.InboundService
|
tgbotService service.Tgbot
|
||||||
|
|
||||||
cron *cron.Cron
|
cron *cron.Cron
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -325,8 +335,13 @@ func (s *Server) startTask() {
|
|||||||
logger.Warning("Add NewStatsNotifyJob error", err)
|
logger.Warning("Add NewStatsNotifyJob error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// listen for TG bot income messages
|
|
||||||
go job.NewStatsNotifyJob().OnReceive()
|
// Check CPU load and alarm to TgBot if threshold passes
|
||||||
|
cpuThreshold, err := s.settingService.GetTgCpu()
|
||||||
|
if (err == nil) && (cpuThreshold > 0) {
|
||||||
|
s.cron.AddJob("@every 10s", job.NewCheckCpuJob())
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
s.cron.Remove(entry)
|
s.cron.Remove(entry)
|
||||||
}
|
}
|
||||||
@@ -403,6 +418,12 @@ func (s *Server) Start() (err error) {
|
|||||||
s.httpServer.Serve(listener)
|
s.httpServer.Serve(listener)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||||
|
if (err == nil) && (isTgbotenabled) {
|
||||||
|
tgBot := s.tgbotService.NewTgbot()
|
||||||
|
tgBot.Start()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,6 +433,9 @@ func (s *Server) Stop() error {
|
|||||||
if s.cron != nil {
|
if s.cron != nil {
|
||||||
s.cron.Stop()
|
s.cron.Stop()
|
||||||
}
|
}
|
||||||
|
if s.tgbotService.IsRunnging() {
|
||||||
|
s.tgbotService.Stop()
|
||||||
|
}
|
||||||
var err1 error
|
var err1 error
|
||||||
var err2 error
|
var err2 error
|
||||||
if s.httpServer != nil {
|
if s.httpServer != 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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user