mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-19 17:15:49 +00:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7419592626 | ||
|
|
edabfad559 | ||
|
|
2849cc0b2e | ||
|
|
46eb174af1 | ||
|
|
f8878208ca | ||
|
|
e126095949 | ||
|
|
df2f292b68 | ||
|
|
9f5ba0cf93 | ||
|
|
b5c5539501 | ||
|
|
252afe47c0 | ||
|
|
379451135d | ||
|
|
bc06dbab21 | ||
|
|
6a71ea7f5e | ||
|
|
942b9862d8 | ||
|
|
ae55fdc38a | ||
|
|
cc3ff61ae2 | ||
|
|
045717010a | ||
|
|
aae32d9211 | ||
|
|
cd5ad78f56 | ||
|
|
b450aacebd | ||
|
|
640068b279 | ||
|
|
04d85af40e | ||
|
|
fca882ee31 | ||
|
|
2832106bc6 | ||
|
|
bf24838939 | ||
|
|
16e3107d23 | ||
|
|
262e3c0985 | ||
|
|
2b460bac1a | ||
|
|
d1c4eb9b4c | ||
|
|
dfa3d39ab3 | ||
|
|
2b54d0344e | ||
|
|
f817f922fe | ||
|
|
55f7fcd1b3 | ||
|
|
b0f974a94d | ||
|
|
6bebde4105 | ||
|
|
fa4a63c958 | ||
|
|
11def0a753 | ||
|
|
513f87550a | ||
|
|
81838b504c | ||
|
|
4980744793 | ||
|
|
5ff6f4094e | ||
|
|
bbce1eb3f7 | ||
|
|
204f73a692 | ||
|
|
641a7d3e57 | ||
|
|
3f7e819a9b | ||
|
|
834d003ab6 | ||
|
|
c627227893 | ||
|
|
2a8725a7a5 | ||
|
|
ca2d1bb901 | ||
|
|
fa19649286 | ||
|
|
56d75f5293 | ||
|
|
e1132a3f41 | ||
|
|
4d479102ad | ||
|
|
e90c575bfd | ||
|
|
9d02f455cc | ||
|
|
13f67f595c | ||
|
|
25741dcb08 | ||
|
|
a7af62162c | ||
|
|
3e0faecaae | ||
|
|
dc7dbae14a | ||
|
|
dd8c763b21 | ||
|
|
e0e7c102b8 | ||
|
|
f26a7df11b | ||
|
|
72a1b1e3f3 | ||
|
|
dfdb77c491 | ||
|
|
846efe8eb4 | ||
|
|
8b79b5a315 | ||
|
|
c71041a60d | ||
|
|
6865cb108c | ||
|
|
ee2089257a | ||
|
|
9f85ec72a8 | ||
|
|
c4c266205b | ||
|
|
e4afbcea3b | ||
|
|
44cede41fd | ||
|
|
672fd1da19 | ||
|
|
f3fe866af2 | ||
|
|
25e50aa6f1 | ||
|
|
ff3657e15a | ||
|
|
4af626bb4b | ||
|
|
936f2e6ec2 | ||
|
|
3e5984930e | ||
|
|
40a0297499 | ||
|
|
e13015a920 | ||
|
|
ec4755952a | ||
|
|
d8fb83b1f2 | ||
|
|
4fb060d25e | ||
|
|
3eff8022f2 | ||
|
|
19bb28cb26 | ||
|
|
b70ecc12b3 | ||
|
|
1e72a22c96 | ||
|
|
d695ea8192 | ||
|
|
b639a1bbd5 | ||
|
|
4f952963ae | ||
|
|
7f2ef94c7f | ||
|
|
81372de369 | ||
|
|
a3b170d6c4 | ||
|
|
4548755375 |
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
|||||||
name: build x-ui amd64 version
|
name: build x-ui amd64 version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.1
|
- uses: actions/checkout@v3.5.2
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4.0.0
|
uses: actions/setup-go@v4.0.0
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
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
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||||
@@ -50,11 +50,11 @@ jobs:
|
|||||||
name: build x-ui arm64 version
|
name: build x-ui arm64 version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.1
|
- uses: actions/checkout@v3.5.2
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4.0.0
|
uses: actions/setup-go@v4.0.0
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version: "stable"
|
||||||
- name: build linux arm64 version
|
- name: build linux arm64 version
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
||||||
unzip Xray-linux-arm64-v8a.zip
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
|
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat iran.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
.idea
|
.idea
|
||||||
tmp
|
tmp
|
||||||
|
backup/
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
x-ui-*.tar.gz
|
x-ui-*.tar.gz
|
||||||
@@ -9,4 +10,5 @@ x-ui-*.tar.gz
|
|||||||
main
|
main
|
||||||
release/
|
release/
|
||||||
access.log
|
access.log
|
||||||
|
error.log
|
||||||
.cache
|
.cache
|
||||||
|
|||||||
124
README.md
124
README.md
@@ -1,13 +1,15 @@
|
|||||||
# 3x-ui
|
# 3x-ui
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](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**
|
||||||
|
|
||||||
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
||||||
|
|
||||||
# Install & Upgrade
|
# Install & Upgrade
|
||||||
@@ -15,19 +17,24 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
|||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install custom version
|
## Install custom version
|
||||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.0.9`:
|
|
||||||
|
To install your desired version you can add the version to the end of install command. Example for ver `v1.3.2`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.0.9
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.3.2
|
||||||
```
|
```
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
|
|
||||||
```
|
```
|
||||||
apt-get install certbot -y
|
apt-get install certbot -y
|
||||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
certbot renew --dry-run
|
certbot renew --dry-run
|
||||||
```
|
```
|
||||||
|
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
||||||
|
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
|
||||||
|
|
||||||
# Default settings
|
# Default settings
|
||||||
|
|
||||||
@@ -36,18 +43,63 @@ certbot renew --dry-run
|
|||||||
- database path: /etc/x-ui/x-ui.db
|
- database path: /etc/x-ui/x-ui.db
|
||||||
- xray config path: /usr/local/x-ui/bin/config.json
|
- xray config path: /usr/local/x-ui/bin/config.json
|
||||||
|
|
||||||
before you set ssl on settings
|
Before you set ssl on settings
|
||||||
- http:// ip or domain:2053/xui
|
|
||||||
|
- http://ip:2053/xui
|
||||||
|
- http://domain:2053/xui
|
||||||
|
|
||||||
After you set ssl on settings
|
After you set ssl on settings
|
||||||
|
|
||||||
- https://yourdomain:2053/xui
|
- https://yourdomain:2053/xui
|
||||||
|
|
||||||
# Enable Traffic For Users:
|
# Environment Variables
|
||||||
|
|
||||||
|
| Variable | Type | Default |
|
||||||
|
| -------------- | :--------------------------------------------: | :------------ |
|
||||||
|
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||||
|
| XUI_DEBUG | `boolean` | `false` |
|
||||||
|
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||||
|
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
# Xray Configurations:
|
||||||
|
|
||||||
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||||
- [enable traffic](./media/enable-traffic.txt)
|
|
||||||
- [enable traffic+block all IR IP address](./media/enable-traffic+block-IR-IP.txt)
|
- [traffic](./media/configs/traffic.json)
|
||||||
- [enable traffic+block all IR domain](./media/enable-traffic+block-IR-domain.txt)
|
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
|
||||||
|
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
|
||||||
|
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
|
||||||
|
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
|
||||||
|
|
||||||
|
# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
|
||||||
|
|
||||||
|
If you want to use routing to WARP follow steps as below:
|
||||||
|
|
||||||
|
1. If you already installed warp, you can uninstall using below command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
warp u
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install WARP on **socks proxy mode**:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
|
||||||
|
|
||||||
|
Config Features:
|
||||||
|
|
||||||
|
- Block Ads
|
||||||
|
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
|
||||||
|
- Fix Google 403 error
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
@@ -62,7 +114,8 @@ After you set ssl on settings
|
|||||||
- 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
|
||||||
- fix api routes (user setting will create with api)
|
- Fix api routes (user setting will create with api)
|
||||||
|
- Support to change configs by different items provided in panel
|
||||||
|
|
||||||
# Tg robot use
|
# Tg robot use
|
||||||
|
|
||||||
@@ -79,10 +132,11 @@ Set the robot-related parameters in the panel background, including:
|
|||||||
|
|
||||||
Reference syntax:
|
Reference syntax:
|
||||||
|
|
||||||
- 30 * * * * * //Notify at the 30s of each point
|
- 30 \* \* \* \* \* //Notify at the 30s of each point
|
||||||
- 0 */10 * * * * //Notify at the first second of each 10 minutes
|
- 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)
|
||||||
|
- @weekly // weekly notification
|
||||||
- @every 8h // notify every 8 hours
|
- @every 8h // notify every 8 hours
|
||||||
|
|
||||||
# Telegram Bot Features
|
# Telegram Bot Features
|
||||||
@@ -100,38 +154,48 @@ Reference syntax:
|
|||||||
- Check depleted users
|
- Check depleted users
|
||||||
- Receive backup by request and in periodic reports
|
- Receive backup by request and in periodic reports
|
||||||
|
|
||||||
|
|
||||||
## API routes
|
## API routes
|
||||||
|
|
||||||
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
||||||
- `/xui/API/inbounds` base for following actions:
|
- `/xui/API/inbounds` base for following actions:
|
||||||
|
|
||||||
| Method | Path | Action |
|
| Method | Path | Action |
|
||||||
| ------------- | ------------- | ------------- |
|
| :----: | ---------------------------------- | ------------------------------------------- |
|
||||||
| GET | "/list" | Get all inbounds |
|
| `GET` | `"/list"` | Get all inbounds |
|
||||||
| GET | "/get/:id" | Get inbound with inbound.id |
|
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||||
| POST | "/add" | Add inbound |
|
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
|
||||||
| POST | "/del/:id" | Delete Inbound |
|
| `POST` | `"/add"` | Add inbound |
|
||||||
| POST | "/update/:id" | Update Inbound |
|
| `POST` | `"/del/:id"` | Delete Inbound |
|
||||||
| POST | "/clientIps/:email" | Client Ip address |
|
| `POST` | `"/update/:id"` | Update Inbound |
|
||||||
| POST | "/clearClientIps/:email" | Clear Client Ip address |
|
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
||||||
| POST | "/addClient/" | Add Client to inbound |
|
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
||||||
| POST | "/delClient/:email" | Delete Client |
|
| `POST` | `"/addClient"` | Add Client to inbound |
|
||||||
| POST | "/updateClient/:index" | Update Client |
|
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by UID/Password as clientId |
|
||||||
| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic |
|
| `POST` | `"/updateClient/:clientId"` | Update Client by UID/Password as clientId |
|
||||||
| POST | "/resetAllTraffics" | Reset traffics of all inbounds |
|
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
||||||
| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
||||||
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
|
|
||||||
# A Special Thanks To
|
# A Special Thanks To
|
||||||
|
|
||||||
- [alireza0](https://github.com/alireza0/)
|
- [alireza0](https://github.com/alireza0/)
|
||||||
- [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
|
||||||
|
|
||||||
# Suggestion System
|
# Suggestion System
|
||||||
|
|
||||||
- Ubuntu 20.04+
|
- Ubuntu 20.04+
|
||||||
- Debian 10+
|
- Debian 10+
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
|
|
||||||
|
# Buy Me a Coffee
|
||||||
|
|
||||||
|
[](#)
|
||||||
|
|
||||||
|
```
|
||||||
|
TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC
|
||||||
|
```
|
||||||
|
|
||||||
# Pictures
|
# Pictures
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -45,6 +45,22 @@ func IsDebug() bool {
|
|||||||
return os.Getenv("XUI_DEBUG") == "true"
|
return os.Getenv("XUI_DEBUG") == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDBPath() string {
|
func GetBinFolderPath() string {
|
||||||
return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName())
|
binFolderPath := os.Getenv("XUI_BIN_FOLDER")
|
||||||
|
if binFolderPath == "" {
|
||||||
|
binFolderPath = "bin"
|
||||||
|
}
|
||||||
|
return binFolderPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDBFolderPath() string {
|
||||||
|
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||||
|
if dbFolderPath == "" {
|
||||||
|
dbFolderPath = "/etc/x-ui"
|
||||||
|
}
|
||||||
|
return dbFolderPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDBPath() string {
|
||||||
|
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.2.3
|
1.3.2
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ func initUser() error {
|
|||||||
}
|
}
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
user := &model.User{
|
user := &model.User{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
Password: "admin",
|
Password: "admin",
|
||||||
|
LoginSecret: "",
|
||||||
}
|
}
|
||||||
return db.Create(user).Error
|
return db.Create(user).Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
LoginSecret string `json:"loginSecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Inbound struct {
|
type Inbound struct {
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -14,7 +14,7 @@ require (
|
|||||||
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.3
|
github.com/shirou/gopsutil/v3 v3.23.3
|
||||||
github.com/xtls/xray-core v1.8.0
|
github.com/xtls/xray-core v1.8.1
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.10.0
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.9.0
|
||||||
google.golang.org/grpc v1.54.0
|
google.golang.org/grpc v1.54.0
|
||||||
@@ -24,7 +24,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
github.com/bytedance/sonic v1.8.7 // indirect
|
github.com/bytedance/sonic v1.8.8 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
|
|||||||
35
go.sum
35
go.sum
@@ -11,6 +11,8 @@ github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414
|
|||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
|
github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
|
||||||
github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
|
github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q=
|
||||||
|
github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
@@ -19,6 +21,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
|
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
||||||
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
||||||
@@ -43,7 +46,7 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
|||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
|
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
|
||||||
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
|
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
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/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||||
@@ -61,7 +64,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-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
|
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
|
||||||
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=
|
||||||
@@ -79,7 +82,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
|||||||
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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||||
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.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
@@ -97,7 +100,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/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
||||||
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=
|
||||||
@@ -106,7 +109,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.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
|
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||||
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=
|
||||||
@@ -122,15 +125,15 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
|
|||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/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-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
|
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||||
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
||||||
github.com/refraction-networking/utls v1.2.3-0.20230308205431-4f1df6c200db h1:ULRv/GPW5KYDafE0FACN2no+HTCyQLUtfyOIeyp3GNc=
|
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
||||||
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/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08=
|
github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
|
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
||||||
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.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
||||||
@@ -164,9 +167,9 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
|
|||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/xtls/reality v0.0.0-20230309125256-0d0713b108c8 h1:LLtLxEe3S0Ko+ckqt4t29RLskpNdOZfgjZCC2/Byr50=
|
github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
|
||||||
github.com/xtls/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k=
|
github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
|
||||||
github.com/xtls/xray-core v1.8.0/go.mod h1:i9KWgbLyxg/NT+3+g4nE74Zp3DgTCP3X04YkSfsJeDI=
|
github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
|
||||||
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=
|
||||||
@@ -183,10 +186,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
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.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||||
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.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||||
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=
|
||||||
@@ -227,7 +230,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
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.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||||
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=
|
||||||
|
|||||||
40
install.sh
40
install.sh
@@ -23,23 +23,14 @@ else
|
|||||||
fi
|
fi
|
||||||
echo "The OS release is: $release"
|
echo "The OS release is: $release"
|
||||||
|
|
||||||
arch=$(arch)
|
arch3xui() {
|
||||||
|
case "$(uname -m)" in
|
||||||
if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then
|
x86_64 | x64 | amd64 ) echo 'amd64' ;;
|
||||||
arch="amd64"
|
armv8 | arm64 | aarch64 ) echo 'arm64' ;;
|
||||||
elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
|
* ) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
||||||
arch="arm64"
|
esac
|
||||||
else
|
}
|
||||||
arch="amd64"
|
echo "arch: $(arch3xui)"
|
||||||
echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "arch: ${arch}"
|
|
||||||
|
|
||||||
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, please get in touch with me!"
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
|
|
||||||
os_version=""
|
os_version=""
|
||||||
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
||||||
@@ -79,6 +70,7 @@ install_base() {
|
|||||||
|
|
||||||
#This function will be called when user installed x-ui out of sercurity
|
#This function will be called when user installed x-ui out of sercurity
|
||||||
config_after_install() {
|
config_after_install() {
|
||||||
|
/usr/local/x-ui/x-ui migrate
|
||||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||||
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
||||||
@@ -122,18 +114,18 @@ install_x-ui() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
|
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
|
||||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}"
|
echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
last_version=$1
|
last_version=$1
|
||||||
url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
|
url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz"
|
||||||
echo -e "Begining to install x-ui $1"
|
echo -e "Begining to install x-ui $1"
|
||||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz ${url}
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${red}Download x-ui $1 failed,please check the version exists${plain}"
|
echo -e "${red}Download x-ui $1 failed,please check the version exists ${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -142,10 +134,10 @@ install_x-ui() {
|
|||||||
rm /usr/local/x-ui/ -rf
|
rm /usr/local/x-ui/ -rf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tar zxvf x-ui-linux-${arch}.tar.gz
|
tar zxvf x-ui-linux-$(arch3xui).tar.gz
|
||||||
rm x-ui-linux-${arch}.tar.gz -f
|
rm x-ui-linux-$(arch3xui).tar.gz -f
|
||||||
cd x-ui
|
cd x-ui
|
||||||
chmod +x x-ui bin/xray-linux-${arch}
|
chmod +x x-ui bin/xray-linux-$(arch3xui)
|
||||||
cp -f x-ui.service /etc/systemd/system/
|
cp -f x-ui.service /etc/systemd/system/
|
||||||
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
||||||
chmod +x /usr/local/x-ui/x-ui.sh
|
chmod +x /usr/local/x-ui/x-ui.sh
|
||||||
|
|||||||
57
main.go
57
main.go
@@ -51,8 +51,8 @@ func runWebServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
//信号量捕获处理
|
// Trap shutdown signals
|
||||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
||||||
for {
|
for {
|
||||||
sig := <-sigCh
|
sig := <-sigCh
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ func showSetting(show bool) {
|
|||||||
settingService := service.SettingService{}
|
settingService := service.SettingService{}
|
||||||
port, err := settingService.GetPort()
|
port, err := settingService.GetPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("get current port fialed,error info:", err)
|
fmt.Println("get current port failed,error info:", err)
|
||||||
}
|
}
|
||||||
userService := service.UserService{}
|
userService := service.UserService{}
|
||||||
userModel, err := userService.GetFirstUser()
|
userModel, err := userService.GetFirstUser()
|
||||||
@@ -109,7 +109,7 @@ func showSetting(show bool) {
|
|||||||
if (username == "") || (userpasswd == "") {
|
if (username == "") || (userpasswd == "") {
|
||||||
fmt.Println("current username or password is empty")
|
fmt.Println("current username or password is empty")
|
||||||
}
|
}
|
||||||
fmt.Println("current pannel settings as follows:")
|
fmt.Println("current panel settings as follows:")
|
||||||
fmt.Println("username:", username)
|
fmt.Println("username:", username)
|
||||||
fmt.Println("userpasswd:", userpasswd)
|
fmt.Println("userpasswd:", userpasswd)
|
||||||
fmt.Println("port:", port)
|
fmt.Println("port:", port)
|
||||||
@@ -204,6 +204,37 @@ func updateSetting(port int, username string, password string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateDb() {
|
||||||
|
inboundService := service.InboundService{}
|
||||||
|
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Start migrating database...")
|
||||||
|
inboundService.MigrationRequirements()
|
||||||
|
inboundService.RemoveOrphanedTraffics()
|
||||||
|
fmt.Println("Migration done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeSecret() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userService := service.UserService{}
|
||||||
|
err = userService.RemoveUserSecret()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
err = settingService.SetSecretStatus(false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
runWebServer()
|
runWebServer()
|
||||||
@@ -217,7 +248,7 @@ func main() {
|
|||||||
|
|
||||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
||||||
var dbPath string
|
var dbPath string
|
||||||
v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path")
|
v2uiCmd.StringVar(&dbPath, "db", fmt.Sprintf("%s/v2-ui.db", config.GetDBFolderPath()), "set v2-ui db file path")
|
||||||
|
|
||||||
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
var port int
|
var port int
|
||||||
@@ -229,14 +260,15 @@ func main() {
|
|||||||
var tgbotRuntime string
|
var tgbotRuntime string
|
||||||
var reset bool
|
var reset bool
|
||||||
var show bool
|
var show bool
|
||||||
|
var remove_secret bool
|
||||||
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
||||||
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
||||||
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||||
settingCmd.StringVar(&username, "username", "", "set login username")
|
settingCmd.StringVar(&username, "username", "", "set login username")
|
||||||
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 telegram bot token")
|
||||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time")
|
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
|
||||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegrame bot chat id")
|
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram 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
|
||||||
@@ -246,6 +278,7 @@ func main() {
|
|||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" run run web panel")
|
fmt.Println(" run run web panel")
|
||||||
fmt.Println(" v2-ui migrate form v2-ui")
|
fmt.Println(" v2-ui migrate form v2-ui")
|
||||||
|
fmt.Println(" migrate migrate form other/old x-ui")
|
||||||
fmt.Println(" setting set settings")
|
fmt.Println(" setting set settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +296,8 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
runWebServer()
|
runWebServer()
|
||||||
|
case "migrate":
|
||||||
|
migrateDb()
|
||||||
case "v2-ui":
|
case "v2-ui":
|
||||||
err := v2uiCmd.Parse(os.Args[2:])
|
err := v2uiCmd.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -290,6 +325,12 @@ func main() {
|
|||||||
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
||||||
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
||||||
}
|
}
|
||||||
|
if remove_secret {
|
||||||
|
removeSecret()
|
||||||
|
}
|
||||||
|
if enabletgbot {
|
||||||
|
updateTgbotEnableSts(enabletgbot)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|||||||
88
media/configs/traffic+block-ads+ipv4-google.json
Normal file
88
media/configs/traffic+block-ads+ipv4-google.json
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "warning",
|
||||||
|
"access": "./access.log",
|
||||||
|
"error": "./error.log"
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"tag": "api",
|
||||||
|
"services": ["HandlerService", "LoggerService", "StatsService"]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"tag": "api",
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"port": 62789,
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "blocked",
|
||||||
|
"protocol": "blackhole",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "IPv4",
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {
|
||||||
|
"domainStrategy": "UseIPv4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policy": {
|
||||||
|
"levels": {
|
||||||
|
"0": {
|
||||||
|
"statsUserDownlink": true,
|
||||||
|
"statsUserUplink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"statsInboundDownlink": true,
|
||||||
|
"statsInboundUplink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"domainStrategy": "IPIfNonMatch",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"inboundTag": ["api"],
|
||||||
|
"outboundTag": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"ip": ["geoip:private"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"protocol": ["bittorrent"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"domain": [
|
||||||
|
"geosite:category-ads-all",
|
||||||
|
"geosite:category-ads",
|
||||||
|
"geosite:google-ads",
|
||||||
|
"geosite:spotify-ads"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "IPv4",
|
||||||
|
"domain": ["geosite:google"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stats": {}
|
||||||
|
}
|
||||||
98
media/configs/traffic+block-ads+warp.json
Normal file
98
media/configs/traffic+block-ads+warp.json
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "warning",
|
||||||
|
"access": "./access.log",
|
||||||
|
"error": "./error.log"
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"tag": "api",
|
||||||
|
"services": ["HandlerService", "LoggerService", "StatsService"]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"tag": "api",
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"port": 62789,
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "blocked",
|
||||||
|
"protocol": "blackhole",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "WARP",
|
||||||
|
"protocol": "socks",
|
||||||
|
"settings": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": 40000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policy": {
|
||||||
|
"levels": {
|
||||||
|
"0": {
|
||||||
|
"statsUserDownlink": true,
|
||||||
|
"statsUserUplink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"statsInboundDownlink": true,
|
||||||
|
"statsInboundUplink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"domainStrategy": "IPIfNonMatch",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"inboundTag": ["api"],
|
||||||
|
"outboundTag": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"ip": ["geoip:private"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"protocol": ["bittorrent"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"domain": [
|
||||||
|
"geosite:category-ads-all",
|
||||||
|
"geosite:category-ads",
|
||||||
|
"geosite:google-ads",
|
||||||
|
"geosite:spotify-ads"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "WARP",
|
||||||
|
"domain": [
|
||||||
|
"geosite:google",
|
||||||
|
"geosite:netflix",
|
||||||
|
"geosite:spotify",
|
||||||
|
"geosite:openai"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stats": {}
|
||||||
|
}
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
"loglevel": "warning",
|
||||||
"access": "./access.log"
|
"access": "./access.log",
|
||||||
|
"error": "./error.log"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"services": [
|
"tag": "api",
|
||||||
"HandlerService",
|
"services": ["HandlerService", "LoggerService", "StatsService"]
|
||||||
"LoggerService",
|
|
||||||
"StatsService"
|
|
||||||
],
|
|
||||||
"tag": "api"
|
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
|
"tag": "api",
|
||||||
"listen": "127.0.0.1",
|
"listen": "127.0.0.1",
|
||||||
"port": 62789,
|
"port": 62789,
|
||||||
"protocol": "dokodemo-door",
|
"protocol": "dokodemo-door",
|
||||||
"settings": {
|
"settings": {
|
||||||
"address": "127.0.0.1"
|
"address": "127.0.0.1"
|
||||||
},
|
}
|
||||||
"tag": "api"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@@ -28,16 +25,16 @@
|
|||||||
"settings": {}
|
"settings": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"tag": "blocked",
|
||||||
"protocol": "blackhole",
|
"protocol": "blackhole",
|
||||||
"settings": {},
|
"settings": {}
|
||||||
"tag": "blocked"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"policy": {
|
"policy": {
|
||||||
"levels": {
|
"levels": {
|
||||||
"0": {
|
"0": {
|
||||||
"statsUserUplink": true,
|
"statsUserDownlink": true,
|
||||||
"statsUserDownlink": true
|
"statsUserUplink": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
@@ -49,34 +46,29 @@
|
|||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "IPIfNonMatch",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"inboundTag": [
|
"type": "field",
|
||||||
"api"
|
"inboundTag": ["api"],
|
||||||
],
|
"outboundTag": "api"
|
||||||
"outboundTag": "api",
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip": [
|
"type": "field",
|
||||||
"geoip:private"
|
|
||||||
],
|
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"type": "field"
|
"ip": ["geoip:private"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": [
|
"protocol": ["bittorrent"]
|
||||||
"bittorrent"
|
|
||||||
],
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"domain": [
|
"domain": [
|
||||||
"regexp:.+.ir$",
|
"regexp:.*\\.ir$",
|
||||||
"ext:iran.dat:ir",
|
"ext:iran.dat:ir",
|
||||||
"ext:iran.dat:other"
|
"ext:iran.dat:other",
|
||||||
],
|
"geosite:category-ir"
|
||||||
"type": "field"
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
"loglevel": "warning",
|
||||||
"access": "./access.log"
|
"access": "./access.log",
|
||||||
|
"error": "./error.log"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"services": [
|
"tag": "api",
|
||||||
"HandlerService",
|
"services": ["HandlerService", "LoggerService", "StatsService"]
|
||||||
"LoggerService",
|
|
||||||
"StatsService"
|
|
||||||
],
|
|
||||||
"tag": "api"
|
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
|
"tag": "api",
|
||||||
"listen": "127.0.0.1",
|
"listen": "127.0.0.1",
|
||||||
"port": 62789,
|
"port": 62789,
|
||||||
"protocol": "dokodemo-door",
|
"protocol": "dokodemo-door",
|
||||||
"settings": {
|
"settings": {
|
||||||
"address": "127.0.0.1"
|
"address": "127.0.0.1"
|
||||||
},
|
}
|
||||||
"tag": "api"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@@ -28,16 +25,16 @@
|
|||||||
"settings": {}
|
"settings": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"tag": "blocked",
|
||||||
"protocol": "blackhole",
|
"protocol": "blackhole",
|
||||||
"settings": {},
|
"settings": {}
|
||||||
"tag": "blocked"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"policy": {
|
"policy": {
|
||||||
"levels": {
|
"levels": {
|
||||||
"0": {
|
"0": {
|
||||||
"statsUserUplink": true,
|
"statsUserDownlink": true,
|
||||||
"statsUserDownlink": true
|
"statsUserUplink": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
@@ -49,32 +46,29 @@
|
|||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "IPIfNonMatch",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"inboundTag": [
|
"type": "field",
|
||||||
"api"
|
"inboundTag": ["api"],
|
||||||
],
|
"outboundTag": "api"
|
||||||
"outboundTag": "api",
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": [
|
"ip": ["geoip:private"]
|
||||||
"bittorrent"
|
|
||||||
],
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": [
|
"protocol": ["bittorrent"]
|
||||||
"geoip:private"
|
|
||||||
],
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": [
|
"ip": ["geoip:private"]
|
||||||
"geoip:ir"
|
},
|
||||||
],
|
{
|
||||||
"type": "field"
|
"type": "field",
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"ip": ["geoip:ir"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
"loglevel": "warning",
|
||||||
"access": "./access.log"
|
"access": "./access.log",
|
||||||
|
"error": "./error.log"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"services": [
|
"tag": "api",
|
||||||
"HandlerService",
|
"services": ["HandlerService", "LoggerService", "StatsService"]
|
||||||
"LoggerService",
|
|
||||||
"StatsService"
|
|
||||||
],
|
|
||||||
"tag": "api"
|
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
|
"tag": "api",
|
||||||
"listen": "127.0.0.1",
|
"listen": "127.0.0.1",
|
||||||
"port": 62789,
|
"port": 62789,
|
||||||
"protocol": "dokodemo-door",
|
"protocol": "dokodemo-door",
|
||||||
"settings": {
|
"settings": {
|
||||||
"address": "127.0.0.1"
|
"address": "127.0.0.1"
|
||||||
},
|
}
|
||||||
"tag": "api"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@@ -28,16 +25,16 @@
|
|||||||
"settings": {}
|
"settings": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"tag": "blocked",
|
||||||
"protocol": "blackhole",
|
"protocol": "blackhole",
|
||||||
"settings": {},
|
"settings": {}
|
||||||
"tag": "blocked"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"policy": {
|
"policy": {
|
||||||
"levels": {
|
"levels": {
|
||||||
"0": {
|
"0": {
|
||||||
"statsUserUplink": true,
|
"statsUserDownlink": true,
|
||||||
"statsUserDownlink": true
|
"statsUserUplink": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
@@ -49,25 +46,19 @@
|
|||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "IPIfNonMatch",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"inboundTag": [
|
"type": "field",
|
||||||
"api"
|
"inboundTag": ["api"],
|
||||||
],
|
"outboundTag": "api"
|
||||||
"outboundTag": "api",
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": [
|
"ip": ["geoip:private"]
|
||||||
"geoip:private"
|
|
||||||
],
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": [
|
"protocol": ["bittorrent"]
|
||||||
"bittorrent"
|
|
||||||
],
|
|
||||||
"type": "field"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -246,6 +246,11 @@
|
|||||||
background-color: #2e3b52;
|
background-color: #2e3b52;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
background-color: #242c3a;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-collapse-item {
|
.ant-card-dark .ant-collapse-item {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #161b22;
|
background-color: #161b22;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ class User {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.username = "";
|
this.username = "";
|
||||||
this.password = "";
|
this.password = "";
|
||||||
|
this.LoginSecret = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +172,7 @@ class AllSetting {
|
|||||||
this.webCertFile = "";
|
this.webCertFile = "";
|
||||||
this.webKeyFile = "";
|
this.webKeyFile = "";
|
||||||
this.webBasePath = "/";
|
this.webBasePath = "/";
|
||||||
|
this.sessionMaxAge = "";
|
||||||
this.expireDiff = "";
|
this.expireDiff = "";
|
||||||
this.trafficDiff = "";
|
this.trafficDiff = "";
|
||||||
this.tgBotEnable = false;
|
this.tgBotEnable = false;
|
||||||
@@ -180,6 +182,7 @@ class AllSetting {
|
|||||||
this.tgBotBackup = false;
|
this.tgBotBackup = false;
|
||||||
this.tgCpu = "";
|
this.tgCpu = "";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
|
this.secretEnable = false;
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const XTLS_FLOW_CONTROL = {
|
|||||||
|
|
||||||
const TLS_FLOW_CONTROL = {
|
const TLS_FLOW_CONTROL = {
|
||||||
VISION: "xtls-rprx-vision",
|
VISION: "xtls-rprx-vision",
|
||||||
|
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||||
};
|
};
|
||||||
|
|
||||||
const TLS_VERSION_OPTION = {
|
const TLS_VERSION_OPTION = {
|
||||||
@@ -91,9 +92,6 @@ const UTLS_FINGERPRINT = {
|
|||||||
UTLS_RANDOMIZED: "randomized",
|
UTLS_RANDOMIZED: "randomized",
|
||||||
};
|
};
|
||||||
|
|
||||||
const bytesToHex = e => Array.from(e).map(e => e.toString(16).padStart(2, 0)).join('');
|
|
||||||
const hexToBytes = e => new Uint8Array(e.match(/[0-9a-f]{2}/gi).map(e => parseInt(e, 16)));
|
|
||||||
|
|
||||||
const ALPN_OPTION = {
|
const ALPN_OPTION = {
|
||||||
H3: "h3",
|
H3: "h3",
|
||||||
H2: "h2",
|
H2: "h2",
|
||||||
@@ -480,8 +478,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
certificates=[new TlsStreamSettings.Cert()],
|
certificates=[new TlsStreamSettings.Cert()],
|
||||||
alpn=[],
|
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||||
settings=[new TlsStreamSettings.Settings()]) {
|
settings=new TlsStreamSettings.Settings()) {
|
||||||
super();
|
super();
|
||||||
this.server = serverName;
|
this.server = serverName;
|
||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
@@ -508,8 +506,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
let values = json.settings[0];
|
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
||||||
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
|
|
||||||
}
|
}
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
@@ -530,7 +527,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
settings: TlsStreamSettings.toJsonArray(this.settings),
|
settings: this.settings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -598,71 +595,204 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
class XtlsStreamSettings extends XrayCommonClass {
|
||||||
|
constructor(serverName='',
|
||||||
|
certificates=[new XtlsStreamSettings.Cert()],
|
||||||
|
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||||
|
settings=new XtlsStreamSettings.Settings()) {
|
||||||
|
super();
|
||||||
|
this.server = serverName;
|
||||||
|
this.certs = certificates;
|
||||||
|
this.alpn = alpn;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
addCert(cert) {
|
||||||
|
this.certs.push(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCert(index) {
|
||||||
|
this.certs.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
let certs;
|
||||||
|
let settings;
|
||||||
|
if (!ObjectUtil.isEmpty(json.certificates)) {
|
||||||
|
certs = json.certificates.map(cert => XtlsStreamSettings.Cert.fromJson(cert));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
|
settings = new XtlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.serverName);
|
||||||
|
}
|
||||||
|
return new XtlsStreamSettings(
|
||||||
|
json.serverName,
|
||||||
|
certs,
|
||||||
|
json.alpn,
|
||||||
|
settings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
serverName: this.server,
|
||||||
|
certificates: XtlsStreamSettings.toJsonArray(this.certs),
|
||||||
|
alpn: this.alpn,
|
||||||
|
settings: this.settings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XtlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
|
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') {
|
||||||
|
super();
|
||||||
|
this.useFile = useFile;
|
||||||
|
this.certFile = certificateFile;
|
||||||
|
this.keyFile = keyFile;
|
||||||
|
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
|
||||||
|
this.key = key instanceof Array ? key.join('\n') : key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
if ('certificateFile' in json && 'keyFile' in json) {
|
||||||
|
return new XtlsStreamSettings.Cert(
|
||||||
|
true,
|
||||||
|
json.certificateFile,
|
||||||
|
json.keyFile,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new XtlsStreamSettings.Cert(
|
||||||
|
false, '', '',
|
||||||
|
json.certificate.join('\n'),
|
||||||
|
json.key.join('\n'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
if (this.useFile) {
|
||||||
|
return {
|
||||||
|
certificateFile: this.certFile,
|
||||||
|
keyFile: this.keyFile,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
certificate: this.cert.split('\n'),
|
||||||
|
key: this.key.split('\n'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
XtlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
|
constructor(allowInsecure = false, serverName = '') {
|
||||||
|
super();
|
||||||
|
this.allowInsecure = allowInsecure;
|
||||||
|
this.serverName = serverName;
|
||||||
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new XtlsStreamSettings.Settings(
|
||||||
|
json.allowInsecure,
|
||||||
|
json.servername,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
allowInsecure: this.allowInsecure,
|
||||||
|
serverName: this.serverName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class RealityStreamSettings extends XrayCommonClass {
|
class RealityStreamSettings extends XrayCommonClass {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
show = false,xver = 0,
|
show = false,xver = 0,
|
||||||
fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX,
|
|
||||||
dest = 'yahoo.com:443',
|
dest = 'yahoo.com:443',
|
||||||
serverNames = 'yahoo.com,www.yahoo.com',
|
serverNames = 'yahoo.com,www.yahoo.com',
|
||||||
privateKey = RandomUtil.randomX25519PrivateKey(),
|
privateKey = '',
|
||||||
publicKey = '',
|
|
||||||
minClient = '',
|
minClient = '',
|
||||||
maxClient = '',
|
maxClient = '',
|
||||||
maxTimediff = 0,
|
maxTimediff = 0,
|
||||||
shortIds = RandomUtil.randowShortId()
|
shortIds = RandomUtil.randowShortId(),
|
||||||
)
|
settings= new RealityStreamSettings.Settings()
|
||||||
{
|
){
|
||||||
super();
|
super();
|
||||||
this.show = show;
|
this.show = show;
|
||||||
this.xver = xver;
|
this.xver = xver;
|
||||||
this.fingerprint = fingerprint;
|
|
||||||
this.dest = dest;
|
this.dest = dest;
|
||||||
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
|
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
this.publicKey = RandomUtil.randomX25519PublicKey(this.privateKey);
|
|
||||||
this.minClient = minClient;
|
this.minClient = minClient;
|
||||||
this.maxClient = maxClient;
|
this.maxClient = maxClient;
|
||||||
this.maxTimediff = maxTimediff;
|
this.maxTimediff = maxTimediff;
|
||||||
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
|
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
let settings;
|
||||||
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
|
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName);
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
|
||||||
return new RealityStreamSettings(
|
return new RealityStreamSettings(
|
||||||
json.show,
|
json.show,
|
||||||
json.xver,
|
json.xver,
|
||||||
json.fingerprint,
|
|
||||||
json.dest,
|
json.dest,
|
||||||
json.serverNames,
|
json.serverNames,
|
||||||
json.privateKey,
|
json.privateKey,
|
||||||
json.publicKey,
|
|
||||||
json.minClient,
|
json.minClient,
|
||||||
json.maxClient,
|
json.maxClient,
|
||||||
json.maxTimediff,
|
json.maxTimediff,
|
||||||
json.shortIds
|
json.shortIds,
|
||||||
|
json.settings,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
toJson() {
|
}
|
||||||
|
toJson() {
|
||||||
return {
|
return {
|
||||||
show: this.show,
|
show: this.show,
|
||||||
xver: this.xver,
|
xver: this.xver,
|
||||||
fingerprint: this.fingerprint,
|
|
||||||
dest: this.dest,
|
dest: this.dest,
|
||||||
serverNames: this.serverNames.split(/,|,|\s+/),
|
serverNames: this.serverNames.split(","),
|
||||||
privateKey: this.privateKey,
|
privateKey: this.privateKey,
|
||||||
publicKey: this.publicKey,
|
|
||||||
minClient: this.minClient,
|
minClient: this.minClient,
|
||||||
maxClient: this.maxClient,
|
maxClient: this.maxClient,
|
||||||
maxTimediff: this.maxTimediff,
|
maxTimediff: this.maxTimediff,
|
||||||
shortIds: this.shortIds.split(/,|,|\s+/)
|
shortIds: this.shortIds.split(","),
|
||||||
};
|
settings: this.settings,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
|
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '') {
|
||||||
|
super();
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
this.serverName = serverName;
|
||||||
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new RealityStreamSettings.Settings(
|
||||||
|
json.publicKey,
|
||||||
|
json.fingerprint,
|
||||||
|
json.serverName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
publicKey: this.publicKey,
|
||||||
|
fingerprint: this.fingerprint,
|
||||||
|
serverName: this.serverName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class StreamSettings extends XrayCommonClass {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
tlsSettings=new TlsStreamSettings(),
|
tlsSettings=new TlsStreamSettings(),
|
||||||
|
xtlsSettings=new XtlsStreamSettings(),
|
||||||
realitySettings = new RealityStreamSettings(),
|
realitySettings = new RealityStreamSettings(),
|
||||||
tcpSettings=new TcpStreamSettings(),
|
tcpSettings=new TcpStreamSettings(),
|
||||||
kcpSettings=new KcpStreamSettings(),
|
kcpSettings=new KcpStreamSettings(),
|
||||||
@@ -675,6 +805,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
this.network = network;
|
this.network = network;
|
||||||
this.security = security;
|
this.security = security;
|
||||||
this.tls = tlsSettings;
|
this.tls = tlsSettings;
|
||||||
|
this.xtls = xtlsSettings;
|
||||||
this.reality = realitySettings;
|
this.reality = realitySettings;
|
||||||
this.tcp = tcpSettings;
|
this.tcp = tcpSettings;
|
||||||
this.kcp = kcpSettings;
|
this.kcp = kcpSettings;
|
||||||
@@ -685,7 +816,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isTls() {
|
get isTls() {
|
||||||
return this.security === 'tls';
|
return this.security === "tls";
|
||||||
}
|
}
|
||||||
|
|
||||||
set isTls(isTls) {
|
set isTls(isTls) {
|
||||||
@@ -696,12 +827,12 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isXTLS() {
|
get isXtls() {
|
||||||
return this.security === "xtls";
|
return this.security === "xtls";
|
||||||
}
|
}
|
||||||
|
|
||||||
set isXTLS(isXTLS) {
|
set isXtls(isXtls) {
|
||||||
if (isXTLS) {
|
if (isXtls) {
|
||||||
this.security = 'xtls';
|
this.security = 'xtls';
|
||||||
} else {
|
} else {
|
||||||
this.security = 'none';
|
this.security = 'none';
|
||||||
@@ -715,27 +846,20 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
|
|
||||||
set isReality(isReality) {
|
set isReality(isReality) {
|
||||||
if (isReality) {
|
if (isReality) {
|
||||||
this.security = "reality";
|
this.security = 'reality';
|
||||||
} else {
|
} else {
|
||||||
this.security = "none";
|
this.security = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json={}) {
|
||||||
let tls, reality;
|
|
||||||
if (json.security === "xtls") {
|
|
||||||
tls = TlsStreamSettings.fromJson(json.XTLSSettings);
|
|
||||||
} else if (json.security === "tls") {
|
|
||||||
tls = TlsStreamSettings.fromJson(json.tlsSettings);
|
|
||||||
}
|
|
||||||
if (json.security === "reality") {
|
|
||||||
reality = RealityStreamSettings.fromJson(json.realitySettings)
|
|
||||||
}
|
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
json.network,
|
json.network,
|
||||||
json.security,
|
json.security,
|
||||||
tls,
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
reality,
|
XtlsStreamSettings.fromJson(json.xtlsSettings),
|
||||||
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||||
WsStreamSettings.fromJson(json.wsSettings),
|
WsStreamSettings.fromJson(json.wsSettings),
|
||||||
@@ -751,9 +875,9 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
network: network,
|
network: network,
|
||||||
security: this.security,
|
security: this.security,
|
||||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||||
XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined,
|
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
|
||||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
|
||||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||||
|
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
@@ -826,22 +950,18 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
set tls(isTls) {
|
set tls(isTls) {
|
||||||
if (isTls) {
|
if (isTls) {
|
||||||
this.xtls = false;
|
|
||||||
this.reality = false;
|
|
||||||
this.stream.security = 'tls';
|
this.stream.security = 'tls';
|
||||||
} else {
|
} else {
|
||||||
this.stream.security = 'none';
|
this.stream.security = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get XTLS() {
|
get xtls() {
|
||||||
return this.stream.security === 'xtls';
|
return this.stream.security === 'xtls';
|
||||||
}
|
}
|
||||||
|
|
||||||
set XTLS(isXTLS) {
|
set xtls(isXtls) {
|
||||||
if (isXTLS) {
|
if (isXtls) {
|
||||||
this.xtls = false;
|
|
||||||
this.reality = false;
|
|
||||||
this.stream.security = 'xtls';
|
this.stream.security = 'xtls';
|
||||||
} else {
|
} else {
|
||||||
this.stream.security = 'none';
|
this.stream.security = 'none';
|
||||||
@@ -850,19 +970,14 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
//for Reality
|
//for Reality
|
||||||
get reality() {
|
get reality() {
|
||||||
if (this.stream.security === "reality") {
|
return this.stream.security === 'reality';
|
||||||
return this.network === "tcp" || this.network === "grpc" || this.network === "http";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set reality(isReality) {
|
set reality(isReality) {
|
||||||
if (isReality) {
|
if (isReality) {
|
||||||
this.tls = false;
|
this.stream.security = 'reality';
|
||||||
this.xtls = false;
|
|
||||||
this.stream.security = "reality";
|
|
||||||
} else {
|
} else {
|
||||||
this.stream.security = "none";
|
this.stream.security = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,7 +1084,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
if (this.stream.isTls || this.stream.isXTLS) {
|
if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) {
|
||||||
return this.stream.tls.server;
|
return this.stream.tls.server;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
@@ -1070,7 +1185,14 @@ class Inbound extends XrayCommonClass {
|
|||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.network === "tcp" || this.network === "grpc" || this.network === "http";
|
switch (this.network) {
|
||||||
|
case "tcp":
|
||||||
|
case "http":
|
||||||
|
case "grpc":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is used for xtls-rprx-vision
|
//this is used for xtls-rprx-vision
|
||||||
@@ -1090,7 +1212,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.canEnableTls();
|
return this.canEnableTls();
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableXTLS() {
|
canEnableXtls() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
@@ -1195,10 +1317,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'],
|
sni: this.stream.tls.settings.serverName,
|
||||||
fp: this.stream.tls.settings[0]['fingerprint'],
|
fp: this.stream.tls.settings.fingerprint,
|
||||||
alpn: this.stream.tls.alpn.join(','),
|
alpn: this.stream.tls.alpn.join(','),
|
||||||
allowInsecure: this.stream.tls.settings[0].allowInsecure,
|
allowInsecure: this.stream.tls.settings.allowInsecure,
|
||||||
};
|
};
|
||||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||||
}
|
}
|
||||||
@@ -1257,51 +1379,49 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
if (this.tls) {
|
if (this.tls) {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.tls.server;
|
||||||
}
|
}
|
||||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
if (this.stream.tls.settings.serverName !== ''){
|
||||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
params.set("sni", this.stream.tls.settings.serverName);
|
||||||
}
|
}
|
||||||
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.XTLS) {
|
if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.xtls.server;
|
||||||
}
|
}
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(/,|,|\s+/)[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.publicKey != "") {
|
if (this.stream.network === 'tcp' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) {
|
||||||
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey));
|
|
||||||
params.set("pbk", this.stream.reality.publicKey);
|
|
||||||
}
|
|
||||||
if (this.stream.network === 'tcp') {
|
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.shortIds != "") {
|
if (this.stream.reality.shortIds.length > 0) {
|
||||||
params.set("sid", this.stream.reality.shortIds);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.fingerprint != "") {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
params.set("fp", this.stream.reality.fingerprint);
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1376,47 +1496,42 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
if (this.tls) {
|
if (this.tls) {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.tls.server;
|
||||||
}
|
}
|
||||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
if (this.stream.tls.settings.serverName !== ''){
|
||||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
params.set("sni", this.stream.tls.settings.serverName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(/,|,|\s+/)[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.publicKey != "") {
|
if (this.stream.reality.shortIds.length > 0) {
|
||||||
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey));
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
params.set("pbk", this.stream.reality.publicKey);
|
|
||||||
}
|
}
|
||||||
if (this.stream.network === 'tcp') {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
|
||||||
if (this.stream.reality.shortIds != "") {
|
|
||||||
params.set("sid", this.stream.reality.shortIds);
|
|
||||||
}
|
|
||||||
if (this.stream.reality.fingerprint != "") {
|
|
||||||
params.set("fp", this.stream.reality.fingerprint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.XTLS) {
|
if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.xtls.server;
|
||||||
}
|
}
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,26 +94,6 @@ const shortIdSeq = [
|
|||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
];
|
];
|
||||||
|
|
||||||
const x25519Map = new Map(
|
|
||||||
[
|
|
||||||
['EH2FWe-Ij_FFAa2u9__-aiErLvVIneP601GOCdlyPWw', "goY3OtfaA4UYbiz7Hn0NysI5QJrK0VT_Chg6RLgUPQU"],
|
|
||||||
['cKI_6DoMSP1IepeWWXrG3G9nkehl94KYBhagU50g2U0', "VigpKFbSLnHLzBWobZaS1IBmw--giJ51w92y723ajnU"],
|
|
||||||
['qM2SNyK3NyHB6deWpEP3ITyCGKQFRTna_mlKP0w1QH0', "HYyIGuyNFslmcnNT7mrDdmuXwn4cm7smE_FZbYguKHQ"],
|
|
||||||
['qCWg5GMEDFd3n1nxDswlIpOHoPUXMLuMOIiLUVzubkI', "rJFC3dUjJxMnVZiUGzmf_LFsJUwFWY-CU5RQgFOHCWM"],
|
|
||||||
['4NOBxDrEsOhNI3Y3EnVIy_TN-uyBoAjQw6QM0YsOi0s', "CbcY9qc4YuMDJDyyL0OITlU824TBg1O84ClPy27e2RM"],
|
|
||||||
['eBvFb0M4HpSOwWjtXV8zliiEs_hg56zX4a2LpuuqpEI', "CjulQ2qVIky7ImIfysgQhNX7s_drGLheCGSkVHcLZhc"],
|
|
||||||
['yEpOzQV04NNcycWVeWtRNTzv5TS-ynTuKRacZCH-6U8', "O9RSr5gSdok2K_tobQnf_scyKVqnCx6C4Jrl7_rCZEQ"],
|
|
||||||
['CNt6TAUVCwqM6xIBHyni0K3Zqbn2htKQLvLb6XDgh0s', "d9cGLVBrDFS02L2OvkqyqwFZ1Ux3AHs28ehl4Rwiyl0"],
|
|
||||||
['EInKw-6Wr0rAHXlxxDuZU5mByIzcD3Z-_iWPzXlUL1k', "LlYD2nNVAvyjNvjZGZh4R8PkMIwkc6EycPTvR2LE0nQ"],
|
|
||||||
['GKIKo7rcXVyle-EUHtGIDtYnDsI6osQmOUl3DTJRAGc', "VcqHivYGGoBkcxOI6cSSjQmneltstkb2OhvO53dyhEM"],
|
|
||||||
['-FVDzv68IC17fJVlNDlhrrgX44WeBfbhwjWpCQVXGHE', "PGG2EYOvsFt2lAQTD7lqHeRxz2KxvllEDKcUrtizPBU"],
|
|
||||||
['0H3OJEYEu6XW7woqy7cKh2vzg6YHkbF_xSDTHKyrsn4', "mzevpYbS8kXengBY5p7tt56QE4tS3lwlwRemmkcQeyc"],
|
|
||||||
['8F8XywN6ci44ES6em2Z0fYYxyptB9uaXY9Hc1WSSPE4', "qCZUdWQZ2H33vWXnOkG8NpxBeq3qn5QWXlfCOWBNkkc"],
|
|
||||||
['IN0dqfkC10dj-ifRHrg2PmmOrzYs697ajGMwcLbu-1g', "2UW_EO3r7uczPGUUlpJBnMDpDmWUHE2yDzCmXS4sckE"],
|
|
||||||
['uIcmks5rAhvBe4dRaJOdeSqgxLGGMZhsGk4J4PEKL2s', "F9WJV_74IZp0Ide4hWjiJXk9FRtBUBkUr3mzU-q1lzk"],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
class RandomUtil {
|
class RandomUtil {
|
||||||
|
|
||||||
static randomIntRange(min, max) {
|
static randomIntRange(min, max) {
|
||||||
@@ -170,26 +150,6 @@ class RandomUtil {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static randowShortId() {
|
|
||||||
let str = '';
|
|
||||||
str += this.randomShortIdSeq(8)
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomX25519PrivateKey() {
|
|
||||||
let num = x25519Map.size;
|
|
||||||
let index = this.randomInt(num);
|
|
||||||
let cntr = 0;
|
|
||||||
for (let key of x25519Map.keys()) {
|
|
||||||
if (cntr++ === index) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomX25519PublicKey(key) {
|
|
||||||
return x25519Map.get(key)
|
|
||||||
}
|
|
||||||
static randomText() {
|
static randomText() {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||||
var string = '';
|
var string = '';
|
||||||
@@ -199,6 +159,12 @@ class RandomUtil {
|
|||||||
}
|
}
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static randowShortId() {
|
||||||
|
let str = '';
|
||||||
|
str += this.randomShortIdSeq(8)
|
||||||
|
return str;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectUtil {
|
class ObjectUtil {
|
||||||
|
|||||||
@@ -19,17 +19,19 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
g.GET("/list", a.getAllInbounds)
|
g.GET("/list", a.getAllInbounds)
|
||||||
g.GET("/get/:id", a.getSingleInbound)
|
g.GET("/get/:id", a.getSingleInbound)
|
||||||
|
g.GET("/getClientTraffics/:email", a.getClientTraffics)
|
||||||
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("/clientIps/:email", a.getClientIps)
|
g.POST("/clientIps/:email", a.getClientIps)
|
||||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||||
g.POST("/addClient/", a.addInboundClient)
|
g.POST("/addClient", a.addInboundClient)
|
||||||
g.POST("/delClient/:email", a.delInboundClient)
|
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
}
|
}
|
||||||
@@ -39,6 +41,9 @@ func (a *APIController) getAllInbounds(c *gin.Context) {
|
|||||||
func (a *APIController) getSingleInbound(c *gin.Context) {
|
func (a *APIController) getSingleInbound(c *gin.Context) {
|
||||||
a.inboundController.getInbound(c)
|
a.inboundController.getInbound(c)
|
||||||
}
|
}
|
||||||
|
func (a *APIController) getClientTraffics(c *gin.Context) {
|
||||||
|
a.inboundController.getClientTraffics(c)
|
||||||
|
}
|
||||||
func (a *APIController) addInbound(c *gin.Context) {
|
func (a *APIController) addInbound(c *gin.Context) {
|
||||||
a.inboundController.addInbound(c)
|
a.inboundController.addInbound(c)
|
||||||
}
|
}
|
||||||
@@ -74,3 +79,6 @@ func (a *APIController) resetAllTraffics(c *gin.Context) {
|
|||||||
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
||||||
a.inboundController.resetAllClientTraffics(c)
|
a.inboundController.resetAllClientTraffics(c)
|
||||||
}
|
}
|
||||||
|
func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||||
|
a.inboundController.delDepletedClients(c)
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
g.POST("/clientIps/:email", a.getClientIps)
|
g.POST("/clientIps/:email", a.getClientIps)
|
||||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||||
g.POST("/addClient/", a.addInboundClient)
|
g.POST("/addClient", a.addInboundClient)
|
||||||
g.POST("/delClient/:email", a.delInboundClient)
|
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +79,16 @@ func (a *InboundController) getInbound(c *gin.Context) {
|
|||||||
jsonObj(c, inbound, nil)
|
jsonObj(c, inbound, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) getClientTraffics(c *gin.Context) {
|
||||||
|
email := c.Param("email")
|
||||||
|
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Error getting traffics", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, clientTraffics, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *InboundController) addInbound(c *gin.Context) {
|
func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
@@ -145,42 +156,41 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
|
|||||||
|
|
||||||
err := a.inboundService.ClearClientIps(email)
|
err := a.inboundService.ClearClientIps(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "修改", err)
|
jsonMsg(c, "Revise", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Log Cleared", nil)
|
jsonMsg(c, "Log Cleared", nil)
|
||||||
}
|
}
|
||||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||||
inbound := &model.Inbound{}
|
data := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.AddInboundClient(inbound)
|
err = a.inboundService.AddInboundClient(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||||
email := c.Param("email")
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
inbound := &model.Inbound{}
|
|
||||||
err := c.ShouldBind(inbound)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
clientId := c.Param("clientId")
|
||||||
|
|
||||||
err = a.inboundService.DelInboundClient(inbound, email)
|
err = a.inboundService.DelInboundClient(id, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client deleted", nil)
|
jsonMsg(c, "Client deleted", nil)
|
||||||
@@ -190,22 +200,18 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||||
index, err := strconv.Atoi(c.Param("index"))
|
clientId := c.Param("clientId")
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err = c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.UpdateInboundClient(inbound, index)
|
err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client updated", nil)
|
jsonMsg(c, "Client updated", nil)
|
||||||
@@ -224,7 +230,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
|
|
||||||
err = a.inboundService.ResetClientTraffic(id, email)
|
err = a.inboundService.ResetClientTraffic(id, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "traffic reseted", nil)
|
jsonMsg(c, "traffic reseted", nil)
|
||||||
@@ -236,7 +242,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||||
err := a.inboundService.ResetAllTraffics()
|
err := a.inboundService.ResetAllTraffics()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics reseted", nil)
|
jsonMsg(c, "All traffics reseted", nil)
|
||||||
@@ -251,8 +257,22 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
|
|
||||||
err = a.inboundService.ResetAllClientTraffics(id)
|
err = a.inboundService.ResetAllClientTraffics(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics of client reseted", nil)
|
jsonMsg(c, "All traffics of client reseted", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) delDepletedClients(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.DelDepletedClients(id)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(c, "All delpeted clients are deleted", nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,15 +11,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LoginForm struct {
|
type LoginForm struct {
|
||||||
Username string `json:"username" form:"username"`
|
Username string `json:"username" form:"username"`
|
||||||
Password string `json:"password" form:"password"`
|
Password string `json:"password" form:"password"`
|
||||||
|
LoginSecret string `json:"loginSecret" form:"loginSecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IndexController struct {
|
type IndexController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
userService service.UserService
|
settingService service.SettingService
|
||||||
tgbot service.Tgbot
|
userService service.UserService
|
||||||
|
tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIndexController(g *gin.RouterGroup) *IndexController {
|
func NewIndexController(g *gin.RouterGroup) *IndexController {
|
||||||
@@ -32,6 +34,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.GET("/", a.index)
|
g.GET("/", a.index)
|
||||||
g.POST("/login", a.login)
|
g.POST("/login", a.login)
|
||||||
g.GET("/logout", a.logout)
|
g.GET("/logout", a.logout)
|
||||||
|
g.POST("/getSecretStatus", a.getSecretStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *IndexController) index(c *gin.Context) {
|
func (a *IndexController) index(c *gin.Context) {
|
||||||
@@ -57,7 +60,7 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword"))
|
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := a.userService.CheckUser(form.Username, form.Password)
|
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
||||||
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 {
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||||
@@ -69,6 +72,16 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||||
|
if err != nil {
|
||||||
|
logger.Infof("Unable to get session's max age from DB")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||||
|
if err != nil {
|
||||||
|
logger.Infof("Unable to set session's max age")
|
||||||
|
}
|
||||||
|
|
||||||
err = session.SetLoginUser(c, user)
|
err = session.SetLoginUser(c, user)
|
||||||
logger.Info("user", user.Id, "login success")
|
logger.Info("user", user.Id, "login success")
|
||||||
jsonMsg(c, I18n(c, "pages.login.toasts.successLogin"), err)
|
jsonMsg(c, I18n(c, "pages.login.toasts.successLogin"), err)
|
||||||
@@ -82,3 +95,11 @@ func (a *IndexController) logout(c *gin.Context) {
|
|||||||
session.ClearSession(c)
|
session.ClearSession(c)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *IndexController) getSecretStatus(c *gin.Context) {
|
||||||
|
status, err := a.settingService.GetSecretStatus()
|
||||||
|
if err == nil {
|
||||||
|
jsonObj(c, status, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/logs/:count", a.getLogs)
|
g.POST("/logs/:count", a.getLogs)
|
||||||
g.POST("/getConfigJson", a.getConfigJson)
|
g.POST("/getConfigJson", a.getConfigJson)
|
||||||
g.GET("/getDb", a.getDb)
|
g.GET("/getDb", a.getDb)
|
||||||
|
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
@@ -114,7 +115,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
|||||||
count := c.Param("count")
|
count := c.Param("count")
|
||||||
logs, err := a.serverService.GetLogs(count)
|
logs, err := a.serverService.GetLogs(count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
jsonMsg(c, "getLogs", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, logs, nil)
|
jsonObj(c, logs, nil)
|
||||||
@@ -123,7 +124,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
|||||||
func (a *ServerController) getConfigJson(c *gin.Context) {
|
func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||||
configJson, err := a.serverService.GetConfigJson()
|
configJson, err := a.serverService.GetConfigJson()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
jsonMsg(c, "get config.json", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, configJson, nil)
|
jsonObj(c, configJson, nil)
|
||||||
@@ -132,7 +133,7 @@ func (a *ServerController) getConfigJson(c *gin.Context) {
|
|||||||
func (a *ServerController) getDb(c *gin.Context) {
|
func (a *ServerController) getDb(c *gin.Context) {
|
||||||
db, err := a.serverService.GetDb()
|
db, err := a.serverService.GetDb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
jsonMsg(c, "get Database", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Set the headers for the response
|
// Set the headers for the response
|
||||||
@@ -142,3 +143,12 @@ func (a *ServerController) getDb(c *gin.Context) {
|
|||||||
// Write the file contents to the response
|
// Write the file contents to the response
|
||||||
c.Writer.Write(db)
|
c.Writer.Write(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
|
||||||
|
cert, err := a.serverService.GetNewX25519Cert()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "get x25519 certificate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, cert, nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ type updateUserForm struct {
|
|||||||
NewPassword string `json:"newPassword" form:"newPassword"`
|
NewPassword string `json:"newPassword" form:"newPassword"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type updateSecretForm struct {
|
||||||
|
LoginSecret string `json:"loginSecret" form:"loginSecret"`
|
||||||
|
}
|
||||||
|
|
||||||
type SettingController struct {
|
type SettingController struct {
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
userService service.UserService
|
userService service.UserService
|
||||||
@@ -37,6 +41,9 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
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)
|
||||||
|
g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
|
||||||
|
g.POST("/updateUserSecret", a.updateSecret)
|
||||||
|
g.POST("/getUserSecret", a.getUserSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
@@ -48,6 +55,15 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
|||||||
jsonObj(c, allSetting, nil)
|
jsonObj(c, allSetting, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
||||||
|
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
expireDiff, err := a.settingService.GetExpireDiff()
|
expireDiff, err := a.settingService.GetExpireDiff()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,3 +134,25 @@ func (a *SettingController) restartPanel(c *gin.Context) {
|
|||||||
err := a.panelService.RestartPanel(time.Second * 3)
|
err := a.panelService.RestartPanel(time.Second * 3)
|
||||||
jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
|
jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SettingController) updateSecret(c *gin.Context) {
|
||||||
|
form := &updateSecretForm{}
|
||||||
|
err := c.ShouldBind(form)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.setting.toasts.modifySetting"), err)
|
||||||
|
}
|
||||||
|
user := session.GetLoginUser(c)
|
||||||
|
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
||||||
|
if err == nil {
|
||||||
|
user.LoginSecret = form.LoginSecret
|
||||||
|
session.SetLoginUser(c, user)
|
||||||
|
}
|
||||||
|
jsonMsg(c, I18n(c, "pages.setting.toasts.modifyUser"), err)
|
||||||
|
}
|
||||||
|
func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||||
|
loginUser := session.GetLoginUser(c)
|
||||||
|
user := a.userService.GetUserSecret(loginUser.Id)
|
||||||
|
if user != nil {
|
||||||
|
jsonObj(c, user, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,14 +29,18 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
|||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
subs, err := a.subService.GetSubs(subId, host)
|
subs, header, err := a.subService.GetSubs(subId, host)
|
||||||
if err != nil {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
result := ""
|
result := ""
|
||||||
for _, sub := range subs {
|
for _, sub := range subs {
|
||||||
result += sub + "\n"
|
result += sub + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add subscription-userinfo
|
||||||
|
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||||
|
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getUriId(c *gin.Context) int64 {
|
|
||||||
s := struct {
|
|
||||||
Id int64 `uri:"id"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
_ = c.BindUri(&s)
|
|
||||||
return s.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRemoteIp(c *gin.Context) string {
|
func getRemoteIp(c *gin.Context) string {
|
||||||
value := c.GetHeader("X-Forwarded-For")
|
value := c.GetHeader("X-Forwarded-For")
|
||||||
if value != "" {
|
if value != "" {
|
||||||
@@ -75,6 +67,7 @@ func html(c *gin.Context, name string, title string, data gin.H) {
|
|||||||
data = gin.H{}
|
data = gin.H{}
|
||||||
}
|
}
|
||||||
data["title"] = title
|
data["title"] = title
|
||||||
|
data["host"] = strings.Split(c.Request.Host, ":")[0]
|
||||||
data["request_uri"] = c.Request.RequestURI
|
data["request_uri"] = c.Request.RequestURI
|
||||||
data["base_path"] = c.GetString("base_path")
|
data["base_path"] = c.GetString("base_path")
|
||||||
c.HTML(http.StatusOK, name, getContext(data))
|
c.HTML(http.StatusOK, name, getContext(data))
|
||||||
@@ -84,10 +77,8 @@ func getContext(h gin.H) gin.H {
|
|||||||
a := gin.H{
|
a := gin.H{
|
||||||
"cur_ver": config.GetVersion(),
|
"cur_ver": config.GetVersion(),
|
||||||
}
|
}
|
||||||
if h != nil {
|
for key, value := range h {
|
||||||
for key, value := range h {
|
a[key] = value
|
||||||
a[key] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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"`
|
||||||
|
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||||
@@ -42,6 +43,7 @@ type AllSetting struct {
|
|||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
|
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
|
|||||||
@@ -13,6 +13,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -57,6 +57,11 @@
|
|||||||
<a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
|
<a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item v-if="secretEnable">
|
||||||
|
<a-input type="text" placeholder='{{ i18n "secretToken" }}' v-model.trim="user.loginSecret" @keydown.enter.native="login">
|
||||||
|
<a-icon slot="prefix" type="key" style="color: rgba(0,0,0,.25)"/>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button>
|
<a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -98,10 +103,12 @@
|
|||||||
data: {
|
data: {
|
||||||
loading: false,
|
loading: false,
|
||||||
user: new User(),
|
user: new User(),
|
||||||
|
secretEnable: false,
|
||||||
lang : ""
|
lang : ""
|
||||||
},
|
},
|
||||||
created(){
|
created(){
|
||||||
this.lang = getLang();
|
this.lang = getLang();
|
||||||
|
this.secretEnable = this.getSecretStatus();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async login() {
|
async login() {
|
||||||
@@ -111,6 +118,15 @@
|
|||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
location.href = basePath + 'xui/';
|
location.href = basePath + 'xui/';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async getSecretStatus() {
|
||||||
|
this.loading= true;
|
||||||
|
const msg = await HttpUtil.post('/getSecretStatus');
|
||||||
|
this.loading = false;
|
||||||
|
if (msg.success){
|
||||||
|
this.secretEnable = msg.obj;
|
||||||
|
return msg.obj;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,6 +33,30 @@
|
|||||||
<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>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input type="number" v-model.number="clientsBulkModal.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
|
||||||
|
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<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>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
||||||
|
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="Subscription">
|
<a-form-item label="Subscription">
|
||||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -51,10 +75,10 @@
|
|||||||
</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 label="{{ i18n "pages.client.delayedStart" }}">
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientsBulkModal.delayedStart">
|
<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-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
@@ -83,9 +107,9 @@
|
|||||||
confirm: null,
|
confirm: null,
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
clients: [],
|
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
totalGB: 0,
|
totalGB: 0,
|
||||||
|
limitIp: 0,
|
||||||
expiryTime: '',
|
expiryTime: '',
|
||||||
emailMethod: 0,
|
emailMethod: 0,
|
||||||
firstNum: 1,
|
firstNum: 1,
|
||||||
@@ -94,8 +118,10 @@
|
|||||||
emailPostfix: "",
|
emailPostfix: "",
|
||||||
subId: "",
|
subId: "",
|
||||||
tgId: "",
|
tgId: "",
|
||||||
|
flow: "",
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
|
clients = [];
|
||||||
method=clientsBulkModal.emailMethod;
|
method=clientsBulkModal.emailMethod;
|
||||||
if(method>1){
|
if(method>1){
|
||||||
start=clientsBulkModal.firstNum;
|
start=clientsBulkModal.firstNum;
|
||||||
@@ -113,11 +139,18 @@
|
|||||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||||
newClient.subId = clientsBulkModal.subId;
|
newClient.subId = clientsBulkModal.subId;
|
||||||
newClient.tgId = clientsBulkModal.tgId;
|
newClient.tgId = clientsBulkModal.tgId;
|
||||||
|
newClient.limitIp = clientsBulkModal.limitIp;
|
||||||
newClient._totalGB = clientsBulkModal.totalGB;
|
newClient._totalGB = clientsBulkModal.totalGB;
|
||||||
newClient._expiryTime = clientsBulkModal.expiryTime;
|
newClient._expiryTime = clientsBulkModal.expiryTime;
|
||||||
clientsBulkModal.clients.push(newClient);
|
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
||||||
|
newClient.flow = clientsBulkModal.flow;
|
||||||
|
}
|
||||||
|
if(clientsBulkModal.inbound.xtls){
|
||||||
|
newClient.flow = clientsBulkModal.flow;
|
||||||
|
}
|
||||||
|
clients.push(newClient);
|
||||||
}
|
}
|
||||||
ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound);
|
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
@@ -128,15 +161,16 @@
|
|||||||
this.totalGB = 0;
|
this.totalGB = 0;
|
||||||
this.expiryTime = 0;
|
this.expiryTime = 0;
|
||||||
this.emailMethod= 0;
|
this.emailMethod= 0;
|
||||||
|
this.limitIp= 0;
|
||||||
this.firstNum= 1;
|
this.firstNum= 1;
|
||||||
this.lastNum= 1;
|
this.lastNum= 1;
|
||||||
this.emailPrefix= "";
|
this.emailPrefix= "";
|
||||||
this.emailPostfix= "";
|
this.emailPostfix= "";
|
||||||
this.subId= "";
|
this.subId= "";
|
||||||
this.tgId= "";
|
this.tgId= "";
|
||||||
|
this.flow= "";
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
|
|||||||
@@ -12,18 +12,24 @@
|
|||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
title: '',
|
title: '',
|
||||||
okText: '',
|
okText: '',
|
||||||
|
isEdit: false,
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
clients: [],
|
clients: [],
|
||||||
clientStats: [],
|
clientStats: [],
|
||||||
|
oldClientId: "",
|
||||||
index: null,
|
index: null,
|
||||||
clientIps: null,
|
clientIps: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index);
|
if(clientModal.isEdit){
|
||||||
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||||
|
} else {
|
||||||
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=(index, dbInbound)=>{}, isEdit=false }) {
|
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
@@ -34,12 +40,13 @@
|
|||||||
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;
|
this.delayedStart = false;
|
||||||
if (!isEdit){
|
if (isEdit){
|
||||||
this.addClient(this.inbound.protocol, this.clients);
|
|
||||||
} else {
|
|
||||||
if (this.clients[index].expiryTime < 0){
|
if (this.clients[index].expiryTime < 0){
|
||||||
this.delayedStart = true;
|
this.delayedStart = true;
|
||||||
}
|
}
|
||||||
|
this.oldClientId = this.dbInbound.protocol == "trojan" ? this.clients[index].password : this.clients[index].id;
|
||||||
|
} else {
|
||||||
|
this.addClient(this.inbound.protocol, this.clients);
|
||||||
}
|
}
|
||||||
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;
|
||||||
@@ -139,6 +146,24 @@
|
|||||||
}
|
}
|
||||||
document.getElementById("clientIPs").value = ""
|
document.getElementById("clientIPs").value = ""
|
||||||
},
|
},
|
||||||
|
resetClientTraffic(email,dbInboundId,iconElement) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
|
okText: '{{ i18n "reset"}}',
|
||||||
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
|
onOk: async () => {
|
||||||
|
iconElement.disabled = true;
|
||||||
|
const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ email);
|
||||||
|
if (msg.success) {
|
||||||
|
this.clientModal.clientStats.up = 0;
|
||||||
|
this.clientModal.clientStats.down = 0;
|
||||||
|
}
|
||||||
|
iconElement.disabled = false;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'number'">
|
<template v-else-if="type === 'number'">
|
||||||
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)" :min="min"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'textarea'">
|
<template v-else-if="type === 'textarea'">
|
||||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
{{define "component/setting"}}
|
{{define "component/setting"}}
|
||||||
<script>
|
<script>
|
||||||
Vue.component('setting-list-item', {
|
Vue.component('setting-list-item', {
|
||||||
props: ["type", "title", "desc", "value"],
|
props: ["type", "title", "desc", "value", "min"],
|
||||||
template: `{{template "component/settingListItem"}}`,
|
template: `{{template "component/settingListItem"}}`,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -68,14 +68,14 @@
|
|||||||
</a-textarea>
|
</a-textarea>
|
||||||
</a-form>
|
</a-form>
|
||||||
</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" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="client.flow" style="width: 200px" :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>
|
||||||
</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="client.flow" style="width: 150px">
|
<a-select v-model="client.flow" style="width: 200px" :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>
|
||||||
@@ -98,12 +98,16 @@
|
|||||||
[[ sizeFormat(clientStats.down) ]]
|
[[ sizeFormat(clientStats.down) ]]
|
||||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||||
</a-tag>
|
</a-tag>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
|
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="{{ i18n "pages.client.delayedStart" }}">
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientModal.delayedStart">
|
<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-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/trojan"}}
|
{{define "form/trojan"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
@@ -18,6 +18,12 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
<a-form-item label="Password">
|
<a-form-item label="Password">
|
||||||
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
||||||
|
</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>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
@@ -31,7 +37,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></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" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<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>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/vless"}}
|
{{define "form/vless"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
@@ -18,6 +18,12 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
<a-form-item label="ID">
|
<a-form-item label="ID">
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;" ></a-input>
|
<a-input v-model.trim="client.id" style="width: 300px;" ></a-input>
|
||||||
|
</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>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
@@ -31,14 +37,14 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></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" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :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" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :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>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/vmess"}}
|
{{define "form/vmess"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
@@ -21,6 +21,12 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
<a-form-item label='{{ i18n "additional" }} ID'>
|
||||||
<a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input>
|
<a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input>
|
||||||
|
</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>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<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>
|
||||||
<a-select-option value="http">HTTP</a-select-option>
|
<a-select-option value="http">H2</a-select-option>
|
||||||
<a-select-option value="quic">QUIC</a-select-option>
|
<a-select-option value="quic">QUIC</a-select-option>
|
||||||
<a-select-option value="grpc">gRPC</a-select-option>
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-switch v-model="inbound.reality"></a-switch>
|
<a-switch v-model="inbound.reality"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.canEnableXTLS()">
|
<a-form-item v-if="inbound.canEnableXtls()">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
XTLS
|
XTLS
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -27,14 +27,14 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-switch v-model="inbound.XTLS"></a-switch>
|
<a-switch v-model="inbound.xtls"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<a-form v-if="inbound.tls || inbound.XTLS" layout="inline">
|
<a-form v-if="inbound.tls" layout="inline">
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls">
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||||
</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">
|
||||||
@@ -52,22 +52,22 @@
|
|||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS" v-if="inbound.tls" >
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px">
|
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="uTLS">
|
||||||
|
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 170px">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<a-select-option value=''>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
|
||||||
<a-input v-model.trim="inbound.stream.tls.server"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Alpn">
|
<a-form-item label="Alpn">
|
||||||
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
|
<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 v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Allow insecure">
|
<a-form-item label="Allow insecure">
|
||||||
<a-switch v-model="inbound.stream.tls.settings[0].allowInsecure"></a-switch>
|
<a-switch v-model="inbound.stream.tls.settings.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">
|
||||||
@@ -93,33 +93,79 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
|
<!-- xtls settings -->
|
||||||
|
<a-form v-if="inbound.xtls" layout="inline">
|
||||||
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Alpn">
|
||||||
|
<a-checkbox-group v-model="inbound.stream.xtls.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.xtls.settings.allowInsecure"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
|
<a-radio-group v-model="inbound.stream.xtls.certs[0].useFile" button-style="solid">
|
||||||
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="inbound.stream.xtls.certs[0].useFile">
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.xtls.certs[0].certFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.xtls.certs[0].keyFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].cert"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- reality settings -->
|
||||||
<a-form v-else-if="inbound.reality" layout="inline">
|
<a-form v-else-if="inbound.reality" layout="inline">
|
||||||
<a-form-item label="show">
|
<a-form-item label="Show">
|
||||||
<a-switch v-model="inbound.stream.reality.show">
|
<a-switch v-model="inbound.stream.reality.show">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xver">
|
<a-form-item label="xVer">
|
||||||
<a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input>
|
<a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS" >
|
<a-form-item label="uTLS" >
|
||||||
<a-select v-model="inbound.stream.reality.fingerprint" style="width: 135px">
|
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 135px">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="dest">
|
<a-form-item label="dest">
|
||||||
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 360px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="serverNames">
|
<a-form-item label="Server Names">
|
||||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 360px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="privateKey">
|
<a-form-item label="ShortIds">
|
||||||
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 360px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="publicKey">
|
|
||||||
<a-input v-model.trim="inbound.stream.reality.publicKey" style="width: 360px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="shortIds">
|
|
||||||
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Private Key">
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Public Key">
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 300px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item >
|
||||||
|
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -49,10 +49,14 @@
|
|||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td v-else-if="inbound.XTLS">
|
<td v-else-if="inbound.xtls">
|
||||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-else-if="inbound.reality">
|
||||||
|
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
|
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
|
</td>
|
||||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -43,6 +43,14 @@
|
|||||||
loading(loading) {
|
loading(loading) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
|
getClients(protocol, clientSettings) {
|
||||||
|
switch(protocol){
|
||||||
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const protocols = {
|
const protocols = {
|
||||||
@@ -62,6 +70,7 @@
|
|||||||
inModal: inModal,
|
inModal: inModal,
|
||||||
Protocols: protocols,
|
Protocols: protocols,
|
||||||
SSMethods: SSMethods,
|
SSMethods: SSMethods,
|
||||||
|
delayedStart: false,
|
||||||
get inbound() {
|
get inbound() {
|
||||||
return inModal.inbound;
|
return inModal.inbound;
|
||||||
},
|
},
|
||||||
@@ -70,36 +79,40 @@
|
|||||||
},
|
},
|
||||||
get isEdit() {
|
get isEdit() {
|
||||||
return inModal.isEdit;
|
return inModal.isEdit;
|
||||||
}
|
},
|
||||||
|
get client() {
|
||||||
|
return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0];
|
||||||
|
},
|
||||||
|
get delayedExpireDays() {
|
||||||
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
|
},
|
||||||
|
set delayedExpireDays(days){
|
||||||
|
this.client.expiryTime = -86400000 * days;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange(oldValue) {
|
streamNetworkChange() {
|
||||||
if (oldValue === 'kcp') {
|
if (!inModal.inbound.canSetTls()) {
|
||||||
this.inModal.inbound.tls = false;
|
this.inModal.inbound.stream.security = 'none';
|
||||||
}
|
}
|
||||||
},
|
if (!inModal.inbound.canEnableReality()) {
|
||||||
addClient(protocol, clients) {
|
this.inModal.inbound.reality = false;
|
||||||
switch (protocol) {
|
|
||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
|
||||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
|
||||||
default: return null;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeClient(index, clients) {
|
|
||||||
clients.splice(index, 1);
|
|
||||||
},
|
|
||||||
isExpiry(index) {
|
|
||||||
return this.inbound.isExpiry(index)
|
|
||||||
},
|
|
||||||
isClientEnable(email) {
|
|
||||||
clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null
|
|
||||||
return clientStats ? clientStats['enable'] : true
|
|
||||||
},
|
|
||||||
setDefaultCertData(){
|
setDefaultCertData(){
|
||||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||||
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
||||||
},
|
},
|
||||||
|
async getNewX25519Cert(){
|
||||||
|
inModal.loading(true);
|
||||||
|
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||||
|
inModal.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||||
|
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||||
|
},
|
||||||
getNewEmail(client) {
|
getNewEmail(client) {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||||
var string = '';
|
var string = '';
|
||||||
|
|||||||
@@ -41,19 +41,19 @@
|
|||||||
<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-popover title="{{ i18n "disabled" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title="{{ i18n "depleted" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title="{{ i18n "depletingSoon" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -67,10 +67,29 @@
|
|||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<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-dropdown :trigger="['click']">
|
||||||
<a-button type="primary" icon="reload" @click="resetAllTraffic">{{ i18n "pages.inbounds.resetAllTraffic" }}</a-button>
|
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||||
|
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||||
|
<a-menu-item key="export">
|
||||||
|
<a-icon type="export"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.export" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="resetInbounds">
|
||||||
|
<a-icon type="reload"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||||
|
</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="delDepletedClients">
|
||||||
|
<a-icon type="rest"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
</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"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1300 }"
|
:loading="spinning" :scroll="{ x: 1300 }"
|
||||||
@@ -78,7 +97,7 @@
|
|||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
@change="() => getDBInbounds()">
|
@change="() => getDBInbounds()">
|
||||||
<template slot="action" slot-scope="text, dbInbound">
|
<template slot="action" slot-scope="text, dbInbound">
|
||||||
<a-icon type="edit" style="font-size: 25px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
|
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
|
||||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
|
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
|
||||||
@@ -101,12 +120,16 @@
|
|||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="resetClients">
|
<a-menu-item key="resetClients">
|
||||||
<a-icon type="file-done"></a-icon>
|
<a-icon type="file-done"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetAllClientTraffics"}}
|
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
|
||||||
</a-menu-item>
|
</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>
|
||||||
|
<a-menu-item key="delDepletedClients">
|
||||||
|
<a-icon type="rest"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
|
</a-menu-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-menu-item key="showInfo">
|
<a-menu-item key="showInfo">
|
||||||
@@ -133,26 +156,26 @@
|
|||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-tag style="margin:0;" color="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.isTls" color="cyan">TLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXTLS" color="cyan">XTLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">Reality</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">Reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="clients" slot-scope="text, dbInbound">
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
<template v-if="clientCount[dbInbound.id]">
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
<a-popover title="{{ i18n "disabled" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title="{{ i18n "depleted" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title="{{ i18n "depletingSoon" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -390,6 +413,22 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
generalActions(action){
|
||||||
|
switch (action.key) {
|
||||||
|
case "export":
|
||||||
|
this.exportAllLinks();
|
||||||
|
break;
|
||||||
|
case "resetInbounds":
|
||||||
|
this.resetAllTraffic();
|
||||||
|
break;
|
||||||
|
case "resetClients":
|
||||||
|
this.resetAllClientTraffics(-1);
|
||||||
|
break;
|
||||||
|
case "delDepletedClients":
|
||||||
|
this.delDepletedClients(-1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
clickAction(action, dbInbound) {
|
clickAction(action, dbInbound) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "qrcode":
|
case "qrcode":
|
||||||
@@ -422,11 +461,14 @@
|
|||||||
case "delete":
|
case "delete":
|
||||||
this.delInbound(dbInbound.id);
|
this.delInbound(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
case "delDepletedClients":
|
||||||
|
this.delDepletedClients(dbInbound.id)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openCloneInbound(dbInbound) {
|
openCloneInbound(dbInbound) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.cloneInbound"}} ' + dbInbound.remark,
|
title: '{{ i18n "pages.inbounds.cloneInbound"}}' + dbInbound.remark,
|
||||||
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||||
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
@@ -531,9 +573,9 @@
|
|||||||
title: '{{ i18n "pages.client.add"}}',
|
title: '{{ i18n "pages.client.add"}}',
|
||||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound, index) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientModal.loading();
|
clientModal.loading();
|
||||||
await this.addClient(inbound, dbInbound);
|
await this.addClient(clients, dbInboundId);
|
||||||
clientModal.close();
|
clientModal.close();
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
@@ -545,9 +587,9 @@
|
|||||||
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
||||||
okText: '{{ i18n "pages.client.bulk"}}',
|
okText: '{{ i18n "pages.client.bulk"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientsBulkModal.loading();
|
clientsBulkModal.loading();
|
||||||
await this.addClient(inbound, dbInbound);
|
await this.addClient(clients, dbInboundId);
|
||||||
clientsBulkModal.close();
|
clientsBulkModal.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -561,9 +603,9 @@
|
|||||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
index: index,
|
index: index,
|
||||||
confirm: async (inbound, dbInbound, index) => {
|
confirm: async (client, dbInboundId, clientId) => {
|
||||||
clientModal.loading();
|
clientModal.loading();
|
||||||
await this.updateClient(inbound, dbInbound, index);
|
await this.updateClient(client, dbInboundId, clientId);
|
||||||
clientModal.close();
|
clientModal.close();
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
@@ -573,19 +615,19 @@
|
|||||||
firstKey = Object.keys(client)[0];
|
firstKey = Object.keys(client)[0];
|
||||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||||
},
|
},
|
||||||
async addClient(inbound, dbInbound) {
|
async addClient(clients, dbInboundId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInbound.id,
|
id: dbInboundId,
|
||||||
settings: inbound.settings.toString(),
|
settings: '{"clients": [' + clients.toString() +']}',
|
||||||
};
|
};
|
||||||
await this.submit('/xui/inbound/addClient/', data);
|
await this.submit(`/xui/inbound/addClient`, data);
|
||||||
},
|
},
|
||||||
async updateClient(inbound, dbInbound, index) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInbound.id,
|
id: dbInboundId,
|
||||||
settings: inbound.settings.toString(),
|
settings: '{"clients": [' + client.toString() +']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
@@ -615,22 +657,14 @@
|
|||||||
},
|
},
|
||||||
delClient(dbInboundId,client) {
|
delClient(dbInboundId,client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
newDbInbound = new DBInbound(dbInbound);
|
clientId = dbInbound.protocol == "trojan" ? client.password : client.id;
|
||||||
inbound = newDbInbound.toInbound();
|
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
|
||||||
index = this.findIndexOfClient(clients, client);
|
|
||||||
clients.splice(index, 1);
|
|
||||||
const data = {
|
|
||||||
id: dbInboundId,
|
|
||||||
settings: inbound.settings.toString(),
|
|
||||||
};
|
|
||||||
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 : '',
|
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/${dbInboundId}/delClient/${clientId}`),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
@@ -658,8 +692,9 @@
|
|||||||
inbound = dbInbound.toInbound();
|
inbound = dbInbound.toInbound();
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
index = this.findIndexOfClient(clients, client);
|
index = this.findIndexOfClient(clients, client);
|
||||||
clients[index].enable = ! clients[index].enable
|
clients[index].enable = !clients[index].enable;
|
||||||
await this.updateClient(inbound, dbInbound, index);
|
clientId = dbInbound.protocol == "trojan" ? clients[index].password : clients[index].id;
|
||||||
|
await this.updateClient(clients[index],dbInboundId, clientId);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async submit(url, data) {
|
async submit(url, data) {
|
||||||
@@ -699,14 +734,24 @@
|
|||||||
},
|
},
|
||||||
resetAllClientTraffics(dbInboundId) {
|
resetAllClientTraffics(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
title: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
content: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
delDepletedClients(dbInboundId) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||||
|
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
|
okText: '{{ i18n "reset"}}',
|
||||||
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
|
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
||||||
|
})
|
||||||
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(index)
|
return dbInbound.toInbound().isExpiry(index)
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,28 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/logger"
|
"encoding/json"
|
||||||
"x-ui/web/service"
|
"os"
|
||||||
|
"regexp"
|
||||||
|
ss "strings"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"os"
|
"x-ui/logger"
|
||||||
ss "strings"
|
"x-ui/web/service"
|
||||||
"regexp"
|
"x-ui/xray"
|
||||||
"encoding/json"
|
// "strconv"
|
||||||
// "strconv"
|
"github.com/go-cmd/cmd"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"net"
|
|
||||||
"github.com/go-cmd/cmd"
|
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckClientIpJob struct {
|
type CheckClientIpJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
var job *CheckClientIpJob
|
var job *CheckClientIpJob
|
||||||
var disAllowedIps []string
|
var disAllowedIps []string
|
||||||
|
|
||||||
@@ -34,22 +36,22 @@ func (j *CheckClientIpJob) Run() {
|
|||||||
processLogFile()
|
processLogFile()
|
||||||
|
|
||||||
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
||||||
blockedIps := []byte(ss.Join(disAllowedIps,","))
|
blockedIps := []byte(ss.Join(disAllowedIps, ","))
|
||||||
err := os.WriteFile("./bin/blockedIPs", blockedIps, 0755)
|
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processLogFile() {
|
func processLogFile() {
|
||||||
accessLogPath := GetAccessLogPath()
|
accessLogPath := GetAccessLogPath()
|
||||||
if(accessLogPath == "") {
|
if accessLogPath == "" {
|
||||||
logger.Warning("xray log not init in config.json")
|
logger.Warning("xray log not init in config.json")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(accessLogPath)
|
data, err := os.ReadFile(accessLogPath)
|
||||||
InboundClientIps := make(map[string][]string)
|
InboundClientIps := make(map[string][]string)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
// clean log
|
// clean log
|
||||||
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
||||||
@@ -62,66 +64,63 @@ func processLogFile() {
|
|||||||
emailRegx, _ := regexp.Compile(`email:.+`)
|
emailRegx, _ := regexp.Compile(`email:.+`)
|
||||||
|
|
||||||
matchesIp := ipRegx.FindString(line)
|
matchesIp := ipRegx.FindString(line)
|
||||||
if(len(matchesIp) > 0) {
|
if len(matchesIp) > 0 {
|
||||||
ip := string(matchesIp)
|
ip := string(matchesIp)
|
||||||
if( ip == "127.0.0.1" || ip == "1.1.1.1") {
|
if ip == "127.0.0.1" || ip == "1.1.1.1" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
matchesEmail := emailRegx.FindString(line)
|
matchesEmail := emailRegx.FindString(line)
|
||||||
if(matchesEmail == "") {
|
if matchesEmail == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matchesEmail = ss.Split(matchesEmail, "email: ")[1]
|
matchesEmail = ss.Split(matchesEmail, "email: ")[1]
|
||||||
|
|
||||||
if(InboundClientIps[matchesEmail] != nil) {
|
if InboundClientIps[matchesEmail] != nil {
|
||||||
if(contains(InboundClientIps[matchesEmail],ip)){
|
if contains(InboundClientIps[matchesEmail], ip) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
}else{
|
}
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
disAllowedIps = []string{}
|
disAllowedIps = []string{}
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
for clientEmail, ips := range InboundClientIps {
|
||||||
inboundClientIps,err := GetInboundClientIps(clientEmail)
|
inboundClientIps, err := GetInboundClientIps(clientEmail)
|
||||||
sort.Sort(sort.StringSlice(ips))
|
sort.Sort(sort.StringSlice(ips))
|
||||||
if(err != nil){
|
if err != nil {
|
||||||
addInboundClientIps(clientEmail,ips)
|
addInboundClientIps(clientEmail, ips)
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
updateInboundClientIps(inboundClientIps,clientEmail,ips)
|
updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// check if inbound connection is more than limited ip and drop connection
|
// check if inbound connection is more than limited ip and drop connection
|
||||||
LimitDevice := func() { LimitDevice() }
|
LimitDevice := func() { LimitDevice() }
|
||||||
|
|
||||||
stop := schedule(LimitDevice, 1000 *time.Millisecond)
|
stop := schedule(LimitDevice, 1000*time.Millisecond)
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
stop <- true
|
stop <- true
|
||||||
|
|
||||||
}
|
}
|
||||||
func GetAccessLogPath() string {
|
func GetAccessLogPath() string {
|
||||||
|
|
||||||
config, err := os.ReadFile("bin/config.json")
|
config, err := os.ReadFile(xray.GetConfigPath())
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
jsonConfig := map[string]interface{}{}
|
jsonConfig := map[string]interface{}{}
|
||||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
err = json.Unmarshal([]byte(config), &jsonConfig)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
if(jsonConfig["log"] != nil) {
|
if jsonConfig["log"] != nil {
|
||||||
jsonLog := jsonConfig["log"].(map[string]interface{})
|
jsonLog := jsonConfig["log"].(map[string]interface{})
|
||||||
if(jsonLog["access"] != nil) {
|
if jsonLog["access"] != nil {
|
||||||
|
|
||||||
accessLogPath := jsonLog["access"].(string)
|
accessLogPath := jsonLog["access"].(string)
|
||||||
|
|
||||||
@@ -132,7 +131,7 @@ func GetAccessLogPath() string {
|
|||||||
|
|
||||||
}
|
}
|
||||||
func checkError(e error) {
|
func checkError(e error) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
logger.Warning("client ip job err:", e)
|
logger.Warning("client ip job err:", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,9 +181,9 @@ func addInboundClientIps(clientEmail string, ips []string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail string,ips []string) error {
|
func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) error {
|
||||||
|
|
||||||
jsonIps, err := json.Marshal(ips)
|
jsonIps, err := json.Marshal(ips)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
inboundClientIps.ClientEmail = clientEmail
|
||||||
@@ -195,7 +194,7 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail
|
|||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
if inbound.Settings == "" {
|
if inbound.Settings == "" {
|
||||||
logger.Debug("wrong data ",inbound)
|
logger.Debug("wrong data ", inbound)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,14 +207,14 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail
|
|||||||
|
|
||||||
limitIp := client.LimitIP
|
limitIp := client.LimitIP
|
||||||
|
|
||||||
if(limitIp < len(ips) && limitIp != 0 && inbound.Enable) {
|
if limitIp < len(ips) && limitIp != 0 && inbound.Enable {
|
||||||
|
|
||||||
disAllowedIps = append(disAllowedIps,ips[limitIp:]...)
|
disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Debug("disAllowedIps ",disAllowedIps)
|
logger.Debug("disAllowedIps ", disAllowedIps)
|
||||||
sort.Sort(sort.StringSlice(disAllowedIps))
|
sort.Sort(sort.StringSlice(disAllowedIps))
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Save(inboundClientIps).Error
|
err = db.Save(inboundClientIps).Error
|
||||||
@@ -224,13 +223,13 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func DisableInbound(id int) error{
|
func DisableInbound(id int) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
result := db.Model(model.Inbound{}).
|
result := db.Model(model.Inbound{}).
|
||||||
Where("id = ? and enable = ?", id, true).
|
Where("id = ? and enable = ?", id, true).
|
||||||
Update("enable", false)
|
Update("enable", false)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
logger.Warning("disable inbound with id:",id)
|
logger.Warning("disable inbound with id:", id)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
job.xrayService.SetToNeedRestart()
|
job.xrayService.SetToNeedRestart()
|
||||||
@@ -242,7 +241,7 @@ func DisableInbound(id int) error{
|
|||||||
func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds *model.Inbound
|
var inbounds *model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%" + clientEmail + "%").Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -250,45 +249,44 @@ func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LimitDevice() {
|
func LimitDevice() {
|
||||||
var destIp, destPort, srcIp, srcPort string
|
var destIp, destPort, srcIp, srcPort string
|
||||||
|
|
||||||
localIp,err := LocalIP()
|
localIp, err := LocalIP()
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
c := cmd.NewCmd("bash","-c","ss --tcp | grep -E '" + IPsToRegex(localIp) + "'| awk '{if($1==\"ESTAB\") print $4,$5;}'","| sort | uniq -c | sort -nr | head")
|
c := cmd.NewCmd("bash", "-c", "ss --tcp | grep -E '"+IPsToRegex(localIp)+"'| awk '{if($1==\"ESTAB\") print $4,$5;}'", "| sort | uniq -c | sort -nr | head")
|
||||||
|
|
||||||
<-c.Start()
|
<-c.Start()
|
||||||
if len(c.Status().Stdout) > 0 {
|
if len(c.Status().Stdout) > 0 {
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
||||||
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
||||||
|
|
||||||
for _, row := range c.Status().Stdout {
|
for _, row := range c.Status().Stdout {
|
||||||
|
|
||||||
data := strings.Split(row," ")
|
data := strings.Split(row, " ")
|
||||||
|
|
||||||
if len(data) < 2 {
|
if len(data) < 2 {
|
||||||
continue // Skip this row if it doesn't have at least two elements
|
continue // Skip this row if it doesn't have at least two elements
|
||||||
}
|
}
|
||||||
|
|
||||||
destIp = string(ipRegx.FindString(data[0]))
|
destIp = string(ipRegx.FindString(data[0]))
|
||||||
destPort = portRegx.FindString(data[0])
|
destPort = portRegx.FindString(data[0])
|
||||||
destPort = strings.Replace(destPort,":","",-1)
|
destPort = strings.Replace(destPort, ":", "", -1)
|
||||||
|
|
||||||
srcIp = string(ipRegx.FindString(data[1]))
|
srcIp = string(ipRegx.FindString(data[1]))
|
||||||
srcPort = portRegx.FindString(data[1])
|
srcPort = portRegx.FindString(data[1])
|
||||||
srcPort = strings.Replace(srcPort,":","",-1)
|
srcPort = strings.Replace(srcPort, ":", "", -1)
|
||||||
|
|
||||||
if contains(disAllowedIps,srcIp){
|
if contains(disAllowedIps, srcIp) {
|
||||||
dropCmd := cmd.NewCmd("bash","-c","ss -K dport = " + srcPort)
|
dropCmd := cmd.NewCmd("bash", "-c", "ss -K dport = "+srcPort)
|
||||||
dropCmd.Start()
|
dropCmd.Start()
|
||||||
|
|
||||||
logger.Debug("request droped : ",srcIp,srcPort,"to",destIp,destPort)
|
logger.Debug("request droped : ", srcIp, srcPort, "to", destIp, destPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func LocalIP() ([]string, error) {
|
func LocalIP() ([]string, error) {
|
||||||
// get machine ips
|
// get machine ips
|
||||||
|
|
||||||
@@ -312,24 +310,23 @@ func LocalIP() ([]string, error) {
|
|||||||
ip = v.IP
|
ip = v.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
ips = append(ips,ip.String())
|
ips = append(ips, ip.String())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Debug("System IPs : ",ips)
|
logger.Debug("System IPs : ", ips)
|
||||||
|
|
||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IPsToRegex(ips []string) string {
|
||||||
func IPsToRegex(ips []string) (string){
|
|
||||||
|
|
||||||
regx := ""
|
regx := ""
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
|
regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
|
||||||
|
|
||||||
}
|
}
|
||||||
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
|
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
|
||||||
|
|
||||||
return regx
|
return regx
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
"loglevel": "warning",
|
||||||
"access": "./access.log"
|
"access": "./access.log",
|
||||||
|
"error": "./error.log"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"services": [
|
"tag": "api",
|
||||||
"HandlerService",
|
"services": ["HandlerService", "LoggerService", "StatsService"]
|
||||||
"LoggerService",
|
|
||||||
"StatsService"
|
|
||||||
],
|
|
||||||
"tag": "api"
|
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
|
"tag": "api",
|
||||||
"listen": "127.0.0.1",
|
"listen": "127.0.0.1",
|
||||||
"port": 62789,
|
"port": 62789,
|
||||||
"protocol": "dokodemo-door",
|
"protocol": "dokodemo-door",
|
||||||
"settings": {
|
"settings": {
|
||||||
"address": "127.0.0.1"
|
"address": "127.0.0.1"
|
||||||
},
|
}
|
||||||
"tag": "api"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@@ -28,16 +25,16 @@
|
|||||||
"settings": {}
|
"settings": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"tag": "blocked",
|
||||||
"protocol": "blackhole",
|
"protocol": "blackhole",
|
||||||
"settings": {},
|
"settings": {}
|
||||||
"tag": "blocked"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"policy": {
|
"policy": {
|
||||||
"levels": {
|
"levels": {
|
||||||
"0": {
|
"0": {
|
||||||
"statsUserUplink": true,
|
"statsUserDownlink": true,
|
||||||
"statsUserDownlink": true
|
"statsUserUplink": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
@@ -49,25 +46,19 @@
|
|||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "IPIfNonMatch",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"inboundTag": [
|
"type": "field",
|
||||||
"api"
|
"inboundTag": ["api"],
|
||||||
],
|
"outboundTag": "api"
|
||||||
"outboundTag": "api",
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": [
|
"ip": ["geoip:private"]
|
||||||
"geoip:private"
|
|
||||||
],
|
|
||||||
"type": "field"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": [
|
"protocol": ["bittorrent"]
|
||||||
"bittorrent"
|
|
||||||
],
|
|
||||||
"type": "field"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
@@ -64,28 +65,45 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
|
|||||||
return clients, nil
|
return clients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) {
|
func (s *InboundService) getAllEmails() ([]string, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var emails []string
|
||||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan})
|
err := db.Raw(`
|
||||||
if ignoreId > 0 {
|
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||||
db = db.Where("id != ?", ignoreId)
|
FROM inbounds,
|
||||||
}
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
db = db.Find(&inbounds)
|
`).Scan(&emails).Error
|
||||||
if db.Error != nil {
|
|
||||||
return "", db.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, inbound := range inbounds {
|
if err != nil {
|
||||||
clients, err := s.getClients(inbound)
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return "", err
|
return emails, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) contains(slice []string, str string) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == str {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for _, client := range clients {
|
func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
|
||||||
if emails[client.Email] {
|
allEmails, err := s.getAllEmails()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var emails []string
|
||||||
|
for _, client := range clients {
|
||||||
|
if client.Email != "" {
|
||||||
|
if s.contains(emails, client.Email) {
|
||||||
return client.Email, nil
|
return client.Email, nil
|
||||||
}
|
}
|
||||||
|
if s.contains(allEmails, client.Email) {
|
||||||
|
return client.Email, nil
|
||||||
|
}
|
||||||
|
emails = append(emails, client.Email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -96,16 +114,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
emails := make(map[string]bool)
|
allEmails, err := s.getAllEmails()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var emails []string
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Email != "" {
|
if client.Email != "" {
|
||||||
if emails[client.Email] {
|
if s.contains(emails, client.Email) {
|
||||||
return client.Email, nil
|
return client.Email, nil
|
||||||
}
|
}
|
||||||
emails[client.Email] = true
|
if s.contains(allEmails, client.Email) {
|
||||||
|
return client.Email, nil
|
||||||
|
}
|
||||||
|
emails = append(emails, client.Email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.checkEmailsExist(emails, inbound.Id)
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
|
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
|
||||||
@@ -215,14 +240,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
return inbound, common.NewError("Port already exists:", inbound.Port)
|
return inbound, common.NewError("Port already exists:", inbound.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
|
||||||
if err != nil {
|
|
||||||
return inbound, err
|
|
||||||
}
|
|
||||||
if existEmail != "" {
|
|
||||||
return inbound, common.NewError("Duplicate email:", existEmail)
|
|
||||||
}
|
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
oldInbound, err := s.GetInbound(inbound.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, err
|
return inbound, err
|
||||||
@@ -245,85 +262,127 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
return inbound, db.Save(oldInbound).Error
|
return inbound, db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
clients, err := s.getClients(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(data.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaceClients := settings["clients"].([]interface{})
|
||||||
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if existEmail != "" {
|
if existEmail != "" {
|
||||||
return common.NewError("Duplicate email:", existEmail)
|
return common.NewError("Duplicate email:", existEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, err := s.getClients(inbound)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
var oldSettings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients, err := s.getClients(oldInbound)
|
oldClients := oldSettings["clients"].([]interface{})
|
||||||
|
oldClients = append(oldClients, interfaceClients...)
|
||||||
|
|
||||||
|
oldSettings["clients"] = oldClients
|
||||||
|
|
||||||
|
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = inbound.Settings
|
oldInbound.Settings = string(newSettings)
|
||||||
|
|
||||||
if len(clients[len(clients)-1].Email) > 0 {
|
for _, client := range clients {
|
||||||
s.AddClientStat(inbound.Id, &clients[len(clients)-1])
|
if len(client.Email) > 0 {
|
||||||
}
|
s.AddClientStat(data.Id, &client)
|
||||||
for i := len(oldClients); i < len(clients); i++ {
|
|
||||||
if len(clients[i].Email) > 0 {
|
|
||||||
s.AddClientStat(inbound.Id, &clients[i])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string) error {
|
func (s *InboundService) DelInboundClient(inboundId int, clientId string) error {
|
||||||
db := database.GetDB()
|
oldInbound, err := s.GetInbound(inboundId)
|
||||||
err := s.DelClientStat(db, email)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Delete stats Data Error")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Load Old Data Error")
|
logger.Error("Load Old Data Error")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
oldInbound.Settings = inbound.Settings
|
email := ""
|
||||||
|
client_key := "id"
|
||||||
|
if oldInbound.Protocol == "trojan" {
|
||||||
|
client_key = "password"
|
||||||
|
}
|
||||||
|
|
||||||
|
inerfaceClients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for _, client := range inerfaceClients {
|
||||||
|
c := client.(map[string]interface{})
|
||||||
|
c_id := c[client_key].(string)
|
||||||
|
if c_id == clientId {
|
||||||
|
email = c["email"].(string)
|
||||||
|
} else {
|
||||||
|
newClients = append(newClients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["clients"] = newClients
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbound.Settings = string(newSettings)
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
err = s.DelClientStat(db, email)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Delete stats Data Error")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = s.DelClientIPs(db, email)
|
err = s.DelClientIPs(db, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in delete client IPs")
|
logger.Error("Error in delete client IPs")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error {
|
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
clients, err := s.getClients(data)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if existEmail != "" {
|
|
||||||
return common.NewError("Duplicate email:", existEmail)
|
|
||||||
}
|
|
||||||
|
|
||||||
clients, err := s.getClients(inbound)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(data.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
inerfaceClients := settings["clients"].([]interface{})
|
||||||
|
|
||||||
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -333,29 +392,68 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = inbound.Settings
|
oldEmail := ""
|
||||||
|
clientIndex := 0
|
||||||
|
for index, oldClient := range oldClients {
|
||||||
|
oldClientId := ""
|
||||||
|
if oldInbound.Protocol == "trojan" {
|
||||||
|
oldClientId = oldClient.Password
|
||||||
|
} else {
|
||||||
|
oldClientId = oldClient.ID
|
||||||
|
}
|
||||||
|
if clientId == oldClientId {
|
||||||
|
oldEmail = oldClient.Email
|
||||||
|
clientIndex = index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
||||||
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existEmail != "" {
|
||||||
|
return common.NewError("Duplicate email:", existEmail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldSettings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settingsClients := oldSettings["clients"].([]interface{})
|
||||||
|
settingsClients[clientIndex] = inerfaceClients[0]
|
||||||
|
oldSettings["clients"] = settingsClients
|
||||||
|
|
||||||
|
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbound.Settings = string(newSettings)
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
if len(clients[index].Email) > 0 {
|
if len(clients[0].Email) > 0 {
|
||||||
if len(oldClients[index].Email) > 0 {
|
if len(oldEmail) > 0 {
|
||||||
err = s.UpdateClientStat(oldClients[index].Email, &clients[index])
|
err = s.UpdateClientStat(oldEmail, &clients[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.UpdateClientIPs(db, oldClients[index].Email, clients[index].Email)
|
err = s.UpdateClientIPs(db, oldEmail, clients[0].Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.AddClientStat(inbound.Id, &clients[index])
|
s.AddClientStat(data.Id, &clients[0])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = s.DelClientStat(db, oldClients[index].Email)
|
err = s.DelClientStat(db, oldEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.DelClientIPs(db, oldClients[index].Email)
|
err = s.DelClientIPs(db, oldEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -363,45 +461,35 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
|||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) (err error) {
|
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
db := database.GetDB()
|
// Update traffics in a single transaction
|
||||||
db = db.Model(model.Inbound{})
|
err := database.GetDB().Transaction(func(tx *gorm.DB) error {
|
||||||
tx := db.Begin()
|
for _, traffic := range traffics {
|
||||||
defer func() {
|
if traffic.IsInbound {
|
||||||
if err != nil {
|
update := tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
||||||
tx.Rollback()
|
Updates(map[string]interface{}{
|
||||||
} else {
|
"up": gorm.Expr("up + ?", traffic.Up),
|
||||||
tx.Commit()
|
"down": gorm.Expr("down + ?", traffic.Down),
|
||||||
}
|
})
|
||||||
}()
|
if update.Error != nil {
|
||||||
for _, traffic := range traffics {
|
return update.Error
|
||||||
if traffic.IsInbound {
|
}
|
||||||
err = tx.Where("tag = ?", traffic.Tag).
|
|
||||||
UpdateColumns(map[string]interface{}{
|
|
||||||
"up": gorm.Expr("up + ?", traffic.Up),
|
|
||||||
"down": gorm.Expr("down + ?", traffic.Down)}).Error
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
|
func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
traffics, err = s.adjustTraffics(traffics)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
db = db.Model(xray.ClientTraffic{})
|
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -412,7 +500,32 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = tx.Save(traffics).Error
|
emails := make([]string, 0, len(traffics))
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
emails = append(emails, traffic.Email)
|
||||||
|
}
|
||||||
|
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for dbTraffic_index := range dbClientTraffics {
|
||||||
|
for traffic_index := range traffics {
|
||||||
|
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
|
||||||
|
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
|
||||||
|
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(dbClientTraffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("AddClientTraffic update data ", err)
|
logger.Warning("AddClientTraffic update data ", err)
|
||||||
}
|
}
|
||||||
@@ -420,81 +533,56 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_traffics []*xray.ClientTraffic, err error) {
|
func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.ClientTraffic) ([]*xray.ClientTraffic, error) {
|
||||||
db := database.GetDB()
|
inboundIds := make([]int, 0, len(dbClientTraffics))
|
||||||
dbInbound := db.Model(model.Inbound{})
|
for _, dbClientTraffic := range dbClientTraffics {
|
||||||
txInbound := dbInbound.Begin()
|
if dbClientTraffic.ExpiryTime < 0 {
|
||||||
|
inboundIds = append(inboundIds, dbClientTraffic.InboundId)
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
txInbound.Rollback()
|
|
||||||
} else {
|
|
||||||
txInbound.Commit()
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
for _, traffic := range traffics {
|
|
||||||
inbound := &model.Inbound{}
|
|
||||||
client_traffic := &xray.ClientTraffic{}
|
|
||||||
err := db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(client_traffic).Error
|
|
||||||
if err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
logger.Warning(err, traffic.Email)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
client_traffic.Up += traffic.Up
|
|
||||||
client_traffic.Down += traffic.Down
|
|
||||||
|
|
||||||
err = txInbound.Where("id=?", client_traffic.InboundId).First(inbound).Error
|
|
||||||
if err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
logger.Warning(err, traffic.Email)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// get clients
|
|
||||||
clients, err := s.getClients(inbound)
|
|
||||||
needUpdate := false
|
|
||||||
if err == nil {
|
|
||||||
for client_index, client := range clients {
|
|
||||||
if traffic.Email == client.Email {
|
|
||||||
if client.ExpiryTime < 0 {
|
|
||||||
clients[client_index].ExpiryTime = (time.Now().Unix() * 1000) - client.ExpiryTime
|
|
||||||
needUpdate = true
|
|
||||||
}
|
|
||||||
client_traffic.ExpiryTime = client.ExpiryTime
|
|
||||||
client_traffic.Total = client.TotalGB
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needUpdate {
|
|
||||||
settings := map[string]interface{}{}
|
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
|
||||||
|
|
||||||
// Convert clients to []interface to update clients in settings
|
|
||||||
var clientsInterface []interface{}
|
|
||||||
for _, c := range clients {
|
|
||||||
clientsInterface = append(clientsInterface, interface{}(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
settings["clients"] = clientsInterface
|
|
||||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = txInbound.Where("id=?", inbound.Id).Update("settings", string(modifiedSettings)).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
full_traffics = append(full_traffics, client_traffic)
|
|
||||||
}
|
}
|
||||||
return full_traffics, nil
|
|
||||||
|
if len(inboundIds) > 0 {
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := tx.Model(model.Inbound{}).Where("id IN (?)", inboundIds).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for inbound_index := range inbounds {
|
||||||
|
settings := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
|
clients, ok := settings["clients"].([]interface{})
|
||||||
|
if ok {
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
for traffic_index := range dbClientTraffics {
|
||||||
|
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
||||||
|
oldExpiryTime := c["expiryTime"].(float64)
|
||||||
|
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
|
||||||
|
c["expiryTime"] = newExpiryTime
|
||||||
|
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Save(inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("AddClientTraffic update inbounds ", err)
|
||||||
|
logger.Error(inbounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbClientTraffics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||||
@@ -507,6 +595,16 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
|||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
now := time.Now().Unix() * 1000
|
||||||
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
|
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||||
|
Update("enable", false)
|
||||||
|
err := result.Error
|
||||||
|
count := result.RowsAffected
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
func (s *InboundService) RemoveOrphanedTraffics() {
|
func (s *InboundService) RemoveOrphanedTraffics() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
db.Exec(`
|
db.Exec(`
|
||||||
@@ -518,16 +616,6 @@ func (s *InboundService) RemoveOrphanedTraffics() {
|
|||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
now := time.Now().Unix() * 1000
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
|
||||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
|
||||||
Update("enable", false)
|
|
||||||
err := result.Error
|
|
||||||
count := result.RowsAffected
|
|
||||||
return count, err
|
|
||||||
}
|
|
||||||
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()
|
||||||
|
|
||||||
@@ -594,8 +682,15 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
|||||||
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
|
whereText := "inbound_id "
|
||||||
|
if id == -1 {
|
||||||
|
whereText += " > ?"
|
||||||
|
} else {
|
||||||
|
whereText += " = ?"
|
||||||
|
}
|
||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
Where("inbound_id = ?", id).
|
Where(whereText, id).
|
||||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
@@ -621,6 +716,84 @@ func (s *InboundService) ResetAllTraffics() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
whereText := "inbound_id "
|
||||||
|
if id < 0 {
|
||||||
|
whereText += "> ?"
|
||||||
|
} else {
|
||||||
|
whereText += "= ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
depletedClients := []xray.ClientTraffic{}
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where(whereText+" and enable = ?", id, false).Select("inbound_id, GROUP_CONCAT(email) as email").Group("inbound_id").Find(&depletedClients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, depletedClient := range depletedClients {
|
||||||
|
emails := strings.Split(depletedClient.Email, ",")
|
||||||
|
oldInbound, err := s.GetInbound(depletedClient.InboundId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var oldSettings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldClients := oldSettings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for _, client := range oldClients {
|
||||||
|
deplete := false
|
||||||
|
c := client.(map[string]interface{})
|
||||||
|
for _, email := range emails {
|
||||||
|
if email == c["email"].(string) {
|
||||||
|
deplete = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !deplete {
|
||||||
|
newClients = append(newClients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newClients) > 0 {
|
||||||
|
oldSettings["clients"] = newClients
|
||||||
|
|
||||||
|
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbound.Settings = string(newSettings)
|
||||||
|
err = tx.Save(oldInbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Delete inbound if no client remains
|
||||||
|
s.DelInbound(depletedClient.InboundId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Where(whereText+" and enable = ?", id, false).Delete(xray.ClientTraffic{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
|
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
@@ -651,18 +824,18 @@ func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTr
|
|||||||
return traffics, err
|
return traffics, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic []*xray.ClientTraffic, err error) {
|
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var traffics []*xray.ClientTraffic
|
var traffics []*xray.ClientTraffic
|
||||||
|
|
||||||
err = db.Model(xray.ClientTraffic{}).Where("email like ?", "%"+email+"%").Find(&traffics).Error
|
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return traffics, err
|
return traffics[0], err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
||||||
@@ -736,3 +909,57 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
|
|||||||
}
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) MigrationRequirements() {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for inbound_index := range inbounds {
|
||||||
|
settings := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
|
clients, ok := settings["clients"].([]interface{})
|
||||||
|
if ok {
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
|
||||||
|
// Add email='' if it is not exists
|
||||||
|
if _, ok := c["email"]; !ok {
|
||||||
|
c["email"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove "flow": "xtls-rprx-direct"
|
||||||
|
if _, ok := c["flow"]; ok {
|
||||||
|
if c["flow"] == "xtls-rprx-direct" {
|
||||||
|
c["flow"] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||||
|
}
|
||||||
|
modelClients, err := s.getClients(inbounds[inbound_index])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, modelClient := range modelClients {
|
||||||
|
if len(modelClient.Email) > 0 {
|
||||||
|
var count int64
|
||||||
|
db.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
|
||||||
|
if count == 0 {
|
||||||
|
s.AddClientStat(inbounds[inbound_index].Id, &modelClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.Save(inbounds)
|
||||||
|
}
|
||||||
|
|||||||
@@ -323,6 +323,10 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = copyZipFile("iran.dat", xray.GetIranPath())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
@@ -390,3 +394,29 @@ func (s *ServerService) GetDb() ([]byte, error) {
|
|||||||
|
|
||||||
return fileContents, nil
|
return fileContents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
|
||||||
|
privateKeyLine := strings.Split(lines[0], ":")
|
||||||
|
publicKeyLine := strings.Split(lines[1], ":")
|
||||||
|
|
||||||
|
privateKey := strings.TrimSpace(privateKeyLine[1])
|
||||||
|
publicKey := strings.TrimSpace(publicKeyLine[1])
|
||||||
|
|
||||||
|
keyPair := map[string]interface{}{
|
||||||
|
"privateKey": privateKey,
|
||||||
|
"publicKey": publicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -28,6 +29,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
"webBasePath": "/",
|
"webBasePath": "/",
|
||||||
|
"sessionMaxAge": "0",
|
||||||
"expireDiff": "0",
|
"expireDiff": "0",
|
||||||
"trafficDiff": "0",
|
"trafficDiff": "0",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Asia/Tehran",
|
||||||
@@ -37,11 +39,21 @@ var defaultValueMap = map[string]string{
|
|||||||
"tgRunTime": "@daily",
|
"tgRunTime": "@daily",
|
||||||
"tgBotBackup": "false",
|
"tgBotBackup": "false",
|
||||||
"tgCpu": "0",
|
"tgCpu": "0",
|
||||||
|
"secretEnable": "false",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) {
|
||||||
|
var jsonData interface{}
|
||||||
|
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return jsonData, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
settings := make([]*model.Setting, 0)
|
settings := make([]*model.Setting, 0)
|
||||||
@@ -119,7 +131,13 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
|||||||
|
|
||||||
func (s *SettingService) ResetSettings() error {
|
func (s *SettingService) ResetSettings() error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
return db.Where("1 = 1").Delete(model.Setting{}).Error
|
err := db.Where("1 = 1").Delete(model.Setting{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.Model(model.User{}).
|
||||||
|
Where("1 = 1").
|
||||||
|
Update("login_secret", "").Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
||||||
@@ -234,18 +252,10 @@ func (s *SettingService) GetTgBotBackup() (bool, error) {
|
|||||||
return s.getBool("tgBotBackup")
|
return s.getBool("tgBotBackup")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetTgBotBackup(value bool) error {
|
|
||||||
return s.setBool("tgBotBackup", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetTgCpu() (int, error) {
|
func (s *SettingService) GetTgCpu() (int, error) {
|
||||||
return s.getInt("tgCpu")
|
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")
|
||||||
}
|
}
|
||||||
@@ -266,16 +276,20 @@ func (s *SettingService) GetExpireDiff() (int, error) {
|
|||||||
return s.getInt("expireDiff")
|
return s.getInt("expireDiff")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetExpireDiff(value int) error {
|
|
||||||
return s.setInt("expireDiff", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetTrafficDiff() (int, error) {
|
func (s *SettingService) GetTrafficDiff() (int, error) {
|
||||||
return s.getInt("trafficDiff")
|
return s.getInt("trafficDiff")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetgetTrafficDiff(value int) error {
|
func (s *SettingService) GetSessionMaxAge() (int, error) {
|
||||||
return s.setInt("trafficDiff", value)
|
return s.getInt("sessionMaxAge")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSecretStatus() (bool, error) {
|
||||||
|
return s.getBool("secretEnable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetSecretStatus(value bool) error {
|
||||||
|
return s.setBool("secretEnable", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/xray"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -18,12 +19,15 @@ type SubService struct {
|
|||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
|
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
||||||
s.address = host
|
s.address = host
|
||||||
var result []string
|
var result []string
|
||||||
|
var header string
|
||||||
|
var traffic xray.ClientTraffic
|
||||||
|
var clientTraffics []xray.ClientTraffic
|
||||||
inbounds, err := s.getInboundsBySubId(subId)
|
inbounds, err := s.getInboundsBySubId(subId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.getClients(inbound)
|
clients, err := s.inboundService.getClients(inbound)
|
||||||
@@ -37,22 +41,54 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
|
|||||||
if client.SubID == subId {
|
if client.SubID == subId {
|
||||||
link := s.getLink(inbound, client.Email)
|
link := s.getLink(inbound, client.Email)
|
||||||
result = append(result, link)
|
result = append(result, link)
|
||||||
|
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
for index, clientTraffic := range clientTraffics {
|
||||||
|
if index == 0 {
|
||||||
|
traffic.Up = clientTraffic.Up
|
||||||
|
traffic.Down = clientTraffic.Down
|
||||||
|
traffic.Total = clientTraffic.Total
|
||||||
|
if clientTraffic.ExpiryTime > 0 {
|
||||||
|
traffic.ExpiryTime = clientTraffic.ExpiryTime
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traffic.Up += clientTraffic.Up
|
||||||
|
traffic.Down += clientTraffic.Down
|
||||||
|
if traffic.Total == 0 || clientTraffic.Total == 0 {
|
||||||
|
traffic.Total = 0
|
||||||
|
} else {
|
||||||
|
traffic.Total += clientTraffic.Total
|
||||||
|
}
|
||||||
|
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
|
||||||
|
traffic.ExpiryTime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||||
|
return result, header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email string) xray.ClientTraffic {
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
if traffic.Email == email {
|
||||||
|
return traffic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return xray.ClientTraffic{}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess":
|
case "vmess":
|
||||||
@@ -271,21 +307,29 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
|
|
||||||
if security == "reality" {
|
if security == "reality" {
|
||||||
params["security"] = "reality"
|
params["security"] = "reality"
|
||||||
realitySettings, _ := stream["realitySettings"].(map[string]interface{})
|
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
|
||||||
if realitySettings != nil {
|
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||||
if sniValue, ok := searchKey(realitySettings, "serverNames"); ok {
|
if realitySetting != nil {
|
||||||
|
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||||
sNames, _ := sniValue.([]interface{})
|
sNames, _ := sniValue.([]interface{})
|
||||||
params["sni"], _ = sNames[0].(string)
|
params["sni"], _ = sNames[0].(string)
|
||||||
}
|
}
|
||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySettings, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]interface{})
|
shortIds, _ := sidValue.([]interface{})
|
||||||
params["sid"], _ = shortIds[0].(string)
|
params["sid"], _ = shortIds[0].(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||||
params["fp"], _ = fpValue.(string)
|
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
||||||
|
params["fp"] = fp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
||||||
|
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
||||||
|
address = sname
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +340,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
|
|
||||||
if security == "xtls" {
|
if security == "xtls" {
|
||||||
params["security"] = "xtls"
|
params["security"] = "xtls"
|
||||||
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{})
|
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||||
var alpn []string
|
var alpn []string
|
||||||
for _, a := range alpns {
|
for _, a := range alpns {
|
||||||
@@ -306,15 +350,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
XTLSSettings, _ := searchKey(xtlsSetting, "settings")
|
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||||
if xtlsSetting != nil {
|
if xtlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok {
|
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok {
|
|
||||||
params["fp"], _ = fpValue.(string)
|
params["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||||
if insecure.(bool) {
|
if insecure.(bool) {
|
||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
@@ -440,9 +481,10 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
|
|
||||||
if security == "reality" {
|
if security == "reality" {
|
||||||
params["security"] = "reality"
|
params["security"] = "reality"
|
||||||
realitySettings, _ := stream["realitySettings"].(map[string]interface{})
|
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
|
||||||
if realitySettings != nil {
|
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||||
if sniValue, ok := searchKey(realitySettings, "serverNames"); ok {
|
if realitySetting != nil {
|
||||||
|
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||||
sNames, _ := sniValue.([]interface{})
|
sNames, _ := sniValue.([]interface{})
|
||||||
params["sni"], _ = sNames[0].(string)
|
params["sni"], _ = sNames[0].(string)
|
||||||
}
|
}
|
||||||
@@ -454,7 +496,14 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["sid"], _ = shortIds[0].(string)
|
params["sid"], _ = shortIds[0].(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||||
params["fp"], _ = fpValue.(string)
|
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
||||||
|
params["fp"] = fp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
||||||
|
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
||||||
|
address = sname
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +514,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
|
|
||||||
if security == "xtls" {
|
if security == "xtls" {
|
||||||
params["security"] = "xtls"
|
params["security"] = "xtls"
|
||||||
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{})
|
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||||
var alpn []string
|
var alpn []string
|
||||||
for _, a := range alpns {
|
for _, a := range alpns {
|
||||||
@@ -475,15 +524,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
XTLSSettings, _ := searchKey(xtlsSetting, "settings")
|
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||||
if xtlsSetting != nil {
|
if xtlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok {
|
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok {
|
|
||||||
params["fp"], _ = fpValue.(string)
|
params["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||||
if insecure.(bool) {
|
if insecure.(bool) {
|
||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
@@ -544,7 +590,11 @@ func searchHost(headers interface{}) string {
|
|||||||
switch v.(type) {
|
switch v.(type) {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
hosts, _ := v.([]interface{})
|
hosts, _ := v.([]interface{})
|
||||||
return hosts[0].(string)
|
if len(hosts) > 0 {
|
||||||
|
return hosts[0].(string)
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
case interface{}:
|
case interface{}:
|
||||||
return v.(string)
|
return v.(string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
|
|||||||
case "client_traffic":
|
case "client_traffic":
|
||||||
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
||||||
case "client_commands":
|
case "client_commands":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
|
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Password]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
|
||||||
case "commands":
|
case "commands":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, "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>")
|
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>")
|
||||||
}
|
}
|
||||||
@@ -404,38 +404,36 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) searchClient(chatId int64, email string) {
|
func (t *Tgbot) searchClient(chatId int64, email string) {
|
||||||
traffics, err := t.inboundService.GetClientTrafficByEmail(email)
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
msg := "❌ Something went wrong!"
|
msg := "❌ Something went wrong!"
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(traffics) == 0 {
|
if traffic == nil {
|
||||||
msg := "No result!"
|
msg := "No result!"
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, traffic := range traffics {
|
expiryTime := ""
|
||||||
expiryTime := ""
|
if traffic.ExpiryTime == 0 {
|
||||||
if traffic.ExpiryTime == 0 {
|
expiryTime = "♾Unlimited"
|
||||||
expiryTime = "♾Unlimited"
|
} else if traffic.ExpiryTime < 0 {
|
||||||
} else if traffic.ExpiryTime < 0 {
|
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||||
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
} else {
|
||||||
} else {
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
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) {
|
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ func (s *UserService) GetFirstUser() (*model.User, error) {
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) CheckUser(username string, password string) *model.User {
|
func (s *UserService) CheckUser(username string, password string, secret string) *model.User {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
user := &model.User{}
|
user := &model.User{}
|
||||||
err := db.Model(model.User{}).
|
err := db.Model(model.User{}).
|
||||||
Where("username = ? and password = ?", username, password).
|
Where("username = ? and password = ? and login_secret = ?", username, password, secret).
|
||||||
First(user).
|
First(user).
|
||||||
Error
|
Error
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
@@ -50,6 +50,35 @@ func (s *UserService) UpdateUser(id int, username string, password string) error
|
|||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) UpdateUserSecret(id int, secret string) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
return db.Model(model.User{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("login_secret", secret).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) RemoveUserSecret() error {
|
||||||
|
db := database.GetDB()
|
||||||
|
return db.Model(model.User{}).
|
||||||
|
Where("1 = 1").
|
||||||
|
Update("login_secret", "").
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) GetUserSecret(id int) *model.User {
|
||||||
|
db := database.GetDB()
|
||||||
|
user := &model.User{}
|
||||||
|
err := db.Model(model.User{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
First(user).
|
||||||
|
Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return errors.New("username can not be empty")
|
return errors.New("username can not be empty")
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ 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 {
|
||||||
@@ -119,12 +118,15 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
|
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
|
||||||
delete(c, key)
|
delete(c, key)
|
||||||
}
|
}
|
||||||
|
if c["flow"] == "xtls-rprx-vision-udp443" {
|
||||||
|
c["flow"] = "xtls-rprx-vision"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final_clients = append(final_clients, interface{}(c))
|
final_clients = append(final_clients, interface{}(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
settings["clients"] = final_clients
|
settings["clients"] = final_clients
|
||||||
modifiedSettings, err := json.Marshal(settings)
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package session
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"x-ui/database/model"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"x-ui/database/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,6 +22,15 @@ func SetLoginUser(c *gin.Context, user *model.User) error {
|
|||||||
return s.Save()
|
return s.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetMaxAge(c *gin.Context, maxAge int) error {
|
||||||
|
s := sessions.Default(c)
|
||||||
|
s.Options(sessions.Options{
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: maxAge,
|
||||||
|
})
|
||||||
|
return s.Save()
|
||||||
|
}
|
||||||
|
|
||||||
func GetLoginUser(c *gin.Context) *model.User {
|
func GetLoginUser(c *gin.Context) *model.User {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
obj := s.Get(loginUser)
|
obj := s.Get(loginUser)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"edit" = "Edit"
|
"edit" = "Edit"
|
||||||
"delete" = "Delete"
|
"delete" = "Delete"
|
||||||
"reset" = "Reset"
|
"reset" = "Reset"
|
||||||
"copySuccess" = "Copy successfully"
|
"copySuccess" = "Copied successfully"
|
||||||
"sure" = "Sure"
|
"sure" = "Sure"
|
||||||
"encryption" = "Encryption"
|
"encryption" = "Encryption"
|
||||||
"transmission" = "Transmission"
|
"transmission" = "Transmission"
|
||||||
@@ -40,20 +40,21 @@
|
|||||||
"depletingSoon" = "Depleting soon"
|
"depletingSoon" = "Depleting soon"
|
||||||
"domainName" = "Domain name"
|
"domainName" = "Domain name"
|
||||||
"additional" = "Alter"
|
"additional" = "Alter"
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Certificat"
|
"certificate" = "Certificate"
|
||||||
"fail" = "Fail"
|
"fail" = "Fail"
|
||||||
"success" = " Success"
|
"success" = " Success"
|
||||||
"getVersion" = "Get version"
|
"getVersion" = "Get version"
|
||||||
"install" = "Install"
|
"install" = "Install"
|
||||||
"clients" = "Clients"
|
"clients" = "Clients"
|
||||||
"usage" = "Usage"
|
"usage" = "Usage"
|
||||||
|
"secretToken" = "Secret token"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "System Status"
|
"dashboard" = "System Status"
|
||||||
"inbounds" = "Inbounds"
|
"inbounds" = "Inbounds"
|
||||||
"setting" = "Panel Setting"
|
"setting" = "Panel Setting"
|
||||||
"logout" = "LogOut"
|
"logout" = "Logout"
|
||||||
"link" = "Other"
|
"link" = "Other"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
"loginAgain" = "The login time limit has expired, please log in again"
|
"loginAgain" = "The login time limit has expired, please log in again"
|
||||||
|
|
||||||
[pages.login.toasts]
|
[pages.login.toasts]
|
||||||
"invalidFormData" = "Input Data Format Is Invalid"
|
"invalidFormData" = "Input Data Format is Invalid"
|
||||||
"emptyUsername" = "Please Enter Username"
|
"emptyUsername" = "Please Enter Username"
|
||||||
"emptyPassword" = "Please Enter Password"
|
"emptyPassword" = "Please Enter Password"
|
||||||
"wrongUsernameOrPassword" = "Invalid username or password"
|
"wrongUsernameOrPassword" = "Invalid username or password"
|
||||||
@@ -75,17 +76,17 @@
|
|||||||
"stopXray" = "Stop"
|
"stopXray" = "Stop"
|
||||||
"restartXray" = "Restart"
|
"restartXray" = "Restart"
|
||||||
"xraySwitch" = "Switch Version"
|
"xraySwitch" = "Switch Version"
|
||||||
"xraySwitchClick" = "Click on the version you want to switch"
|
"xraySwitchClick" = "Choose the version you want to switch to."
|
||||||
"xraySwitchClickDesk" = "Please choose carefully, older versions may have incompatible configurations"
|
"xraySwitchClickDesk" = "Choose wisely, as older versions may not be compatible with current configurations."
|
||||||
"operationHours" = "Operation Hours"
|
"operationHours" = "Operation Hours"
|
||||||
"operationHoursDesc" = "The running time of the system since it was started"
|
"operationHoursDesc" = "System uptime: time since startup."
|
||||||
"systemLoad" = "System Load"
|
"systemLoad" = "System Load"
|
||||||
"connectionCount" = "Connection Count"
|
"connectionCount" = "Number of connections"
|
||||||
"connectionCountDesc" = "The total number of connections for all network cards"
|
"connectionCountDesc" = "Total connections across all network cards"
|
||||||
"upSpeed" = "Total upload speed for all network cards"
|
"upSpeed" = "Total upload speed for all network cards"
|
||||||
"downSpeed" = "Total download speed for all network cards"
|
"downSpeed" = "Total download speed for all network cards"
|
||||||
"totalSent" = "Total upload traffic of all network cards since system startup"
|
"totalSent" = "Total upload traffic of all network cards since system startup"
|
||||||
"totalReceive" = "Total download traffic of all network cards since system startup"
|
"totalReceive" = "Total download data across all network cards since system startup"
|
||||||
"xraySwitchVersionDialog" = "Switch xray version"
|
"xraySwitchVersionDialog" = "Switch xray version"
|
||||||
"xraySwitchVersionDialogDesc" = "Whether to switch the xray version to"
|
"xraySwitchVersionDialogDesc" = "Whether to switch the xray version to"
|
||||||
"dontRefreshh" = "Installation is in progress, please do not refresh this page"
|
"dontRefreshh" = "Installation is in progress, please do not refresh this page"
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
"totalDownUp" = "Total uploads/downloads"
|
"totalDownUp" = "Total uploads/downloads"
|
||||||
"totalUsage" = "Total usage"
|
"totalUsage" = "Total usage"
|
||||||
"inboundCount" = "Number of inbound"
|
"inboundCount" = "Number of inbound"
|
||||||
"operate" = "Actions"
|
"operate" = "Menu"
|
||||||
"enable" = "Enable"
|
"enable" = "Enable"
|
||||||
"remark" = "Remark"
|
"remark" = "Remark"
|
||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
@@ -106,12 +107,13 @@
|
|||||||
"expireDate" = "Expire date"
|
"expireDate" = "Expire date"
|
||||||
"resetTraffic" = "Reset traffic"
|
"resetTraffic" = "Reset traffic"
|
||||||
"addInbound" = "Add Inbound"
|
"addInbound" = "Add Inbound"
|
||||||
"addTo" = "Add To"
|
"generalActions" = "General Actions"
|
||||||
"revise" = "Revise"
|
"addTo" = "Create"
|
||||||
|
"revise" = "Update"
|
||||||
"modifyInbound" = "Modify InBound"
|
"modifyInbound" = "Modify InBound"
|
||||||
"deleteInbound" = "Delete Inbound"
|
"deleteInbound" = "Delete Inbound"
|
||||||
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
"deleteInboundContent" = "Confirm deletion of inbound?"
|
||||||
"resetTrafficContent" = "Are you sure you want to reset traffic?"
|
"resetTrafficContent" = "Confirm traffic reset?"
|
||||||
"copyLink" = "Copy Link"
|
"copyLink" = "Copy Link"
|
||||||
"address" = "Address"
|
"address" = "Address"
|
||||||
"network" = "Network"
|
"network" = "Network"
|
||||||
@@ -121,8 +123,8 @@
|
|||||||
"monitorDesc" = "Leave blank by default"
|
"monitorDesc" = "Leave blank by default"
|
||||||
"meansNoLimit" = "Means no limit"
|
"meansNoLimit" = "Means no limit"
|
||||||
"totalFlow" = "Total flow"
|
"totalFlow" = "Total flow"
|
||||||
"leaveBlankToNeverExpire" = "Leave blank to never expire"
|
"leaveBlankToNeverExpire" = "Leave blank to set no expiration"
|
||||||
"noRecommendKeepDefault" = "There are no special requirements to keep the default"
|
"noRecommendKeepDefault" = "No special requirements to maintain default settings"
|
||||||
"certificatePath" = "Certificate file path"
|
"certificatePath" = "Certificate file path"
|
||||||
"certificateContent" = "Certificate file content"
|
"certificateContent" = "Certificate file content"
|
||||||
"publicKeyPath" = "Public key path"
|
"publicKeyPath" = "Public key path"
|
||||||
@@ -134,7 +136,7 @@
|
|||||||
"export" = "Export links"
|
"export" = "Export links"
|
||||||
"Clone" = "Clone"
|
"Clone" = "Clone"
|
||||||
"cloneInbound" = "Create"
|
"cloneInbound" = "Create"
|
||||||
"cloneInboundContent" = "All items of this inbound except Port, Listening IP, Clients will be applied to the clone"
|
"cloneInboundContent" = "All settings of this inbound, except for Port, Listening IP, and Clients, will be applied to the clone"
|
||||||
"cloneInboundOk" = "Creating a clone from"
|
"cloneInboundOk" = "Creating a clone from"
|
||||||
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
||||||
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
||||||
@@ -142,17 +144,23 @@
|
|||||||
"resetAllTrafficOkText" = "Confirm"
|
"resetAllTrafficOkText" = "Confirm"
|
||||||
"resetAllTrafficCancelText" = "Cancel"
|
"resetAllTrafficCancelText" = "Cancel"
|
||||||
"IPLimit" = "IP Limit"
|
"IPLimit" = "IP Limit"
|
||||||
"IPLimitDesc" = "disable inbound if more than entered count (0 for disable limit ip)"
|
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (Enter 0 to disable IP limit)"
|
||||||
"resetAllClientTraffics" = "Reset Clients Traffic"
|
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
||||||
|
"resetInboundClientTrafficTitle" = "Reset all clients traffic"
|
||||||
|
"resetInboundClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
||||||
|
"resetAllClientTraffics" = "Reset All Clients Traffic"
|
||||||
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
||||||
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of all clients ?"
|
||||||
|
"delDepletedClients" = "Delete depleted clients"
|
||||||
|
"delDepletedClientsTitle" = "Delete depleted clients"
|
||||||
|
"delDepletedClientsContent" = "Are you sure to delete all depleted clients ?"
|
||||||
"Email" = "Email"
|
"Email" = "Email"
|
||||||
"EmailDesc" = "The Email Must Be Completely Unique"
|
"EmailDesc" = "Please provide a unique email address"
|
||||||
"IPLimitlog" = "IP Log"
|
"IPLimitlog" = "IP Log"
|
||||||
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
|
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
|
||||||
"IPLimitlogclear" = "Clear The Log"
|
"IPLimitlogclear" = "Clear The Log"
|
||||||
"setDefaultCert" = "Set cert from panel"
|
"setDefaultCert" = "Set cert from panel"
|
||||||
"XTLSdec" = "Xray core needs to be 1.7.5 and below"
|
"XTLSdec" = "Xray core needs to be 1.7.5"
|
||||||
"Realitydec" = "Xray core needs to be 1.8.0 and above"
|
"Realitydec" = "Xray core needs to be 1.8.0 and above"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -196,6 +204,8 @@
|
|||||||
"save" = "Save"
|
"save" = "Save"
|
||||||
"restartPanel" = "Restart Panel"
|
"restartPanel" = "Restart Panel"
|
||||||
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
|
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
|
||||||
|
"actions" = "Actions"
|
||||||
|
"resetDefaultConfig" = "Reset to default config"
|
||||||
"panelConfig" = "Panel Configuration"
|
"panelConfig" = "Panel Configuration"
|
||||||
"userSetting" = "User Setting"
|
"userSetting" = "User Setting"
|
||||||
"xrayConfiguration" = "Xray Configuration"
|
"xrayConfiguration" = "Xray Configuration"
|
||||||
@@ -215,24 +225,59 @@
|
|||||||
"currentPassword" = "Current Password"
|
"currentPassword" = "Current Password"
|
||||||
"newUsername" = "New Username"
|
"newUsername" = "New Username"
|
||||||
"newPassword" = "New Password"
|
"newPassword" = "New Password"
|
||||||
"advancedTemplate" = "Advanced template parts"
|
"basicTemplate" = "Basic Template"
|
||||||
"completeTemplate" = "Complete template of Xray configuration"
|
"advancedTemplate" = "Advanced Template parts"
|
||||||
|
"completeTemplate" = "Complete Template of Xray configuration"
|
||||||
|
"generalConfigs" = "General Configs"
|
||||||
|
"generalConfigsDesc" = "This options will prevent users from connecting to specific protocols and websites."
|
||||||
|
"countryConfigs" = "Country Configs"
|
||||||
|
"countryConfigsDesc" = "This options will prevent users from connecting to specific country domains."
|
||||||
|
"ipv4Configs" = "IPv4 Configs"
|
||||||
|
"ipv4ConfigsDesc" = "This options will be route to target domains only via IPv4."
|
||||||
|
"warpConfigs" = "WARP Configs"
|
||||||
|
"warpConfigsDesc" = "Caution: Before using this options, Install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers."
|
||||||
"xrayConfigTemplate" = "Xray Configuration Template"
|
"xrayConfigTemplate" = "Xray Configuration Template"
|
||||||
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
|
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
|
||||||
"xrayConfigTorrent" = "Ban bittorrent usage"
|
"xrayConfigTorrent" = "Ban bittorrent usage"
|
||||||
"xrayConfigTorrentDesc" = "Change the configuration temlate to avoid using bittorrent by users, restart the panel to take effect"
|
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using bittorrent by users, restart the panel to take effect"
|
||||||
"xrayConfigPrivateIp" = "Ban private IP ranges to connect"
|
"xrayConfigPrivateIp" = "Ban private IP ranges to connect"
|
||||||
"xrayConfigPrivateIpDesc" = "Change the configuration temlate to avoid connecting with private IP ranges, restart the panel to take effect"
|
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges, restart the panel to take effect"
|
||||||
|
"xrayConfigAds" = "Block Ads"
|
||||||
|
"xrayConfigAdsDesc" = "Change the configuration template to block Ads, restart the panel to take effect"
|
||||||
|
"xrayConfigPorn" = "Block Porn Websites"
|
||||||
|
"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to Porn websites, restart the panel to take effect"
|
||||||
"xrayConfigIRIp" = "Ban Iran IP ranges to connect"
|
"xrayConfigIRIp" = "Ban Iran IP ranges to connect"
|
||||||
"xrayConfigIRIpDesc" = "Change the configuration temlate to avoid connecting with Iran IP ranges, restart the panel to take effect"
|
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges, restart the panel to take effect"
|
||||||
"xrayConfigIRdomain" = "Ban IR domains to connect"
|
"xrayConfigIRDomain" = "Ban Iran Domains to connect"
|
||||||
"xrayConfigIRdomainDesc" = "Change the configuration temlate to avoid connecting with IR domains, restart the panel to take effect"
|
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting with Iran domains, restart the panel to take effect"
|
||||||
|
"xrayConfigChinaIp" = "Ban China IP ranges to connect"
|
||||||
|
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting with China IP ranges, restart the panel to take effect"
|
||||||
|
"xrayConfigChinaDomain" = "Ban China Domains to connect"
|
||||||
|
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting with China domains, restart the panel to take effect"
|
||||||
|
"xrayConfigRussiaIp" = "Ban Russia IP ranges to connect"
|
||||||
|
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting with Russia IP ranges, restart the panel to take effect"
|
||||||
|
"xrayConfigRussiaDomain" = "Ban Russia Domains to connect"
|
||||||
|
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting with Russia domains, restart the panel to take effect"
|
||||||
|
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
|
||||||
|
"xrayConfigGoogleIPv4Desc" = "Add routing for google to connect with IPv4, restart the panel to take effect"
|
||||||
|
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
|
||||||
|
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4, restart the panel to take effect"
|
||||||
|
"xrayConfigGoogleWARP" = "Route Google to WARP"
|
||||||
|
"xrayConfigGoogleWARPDesc" = "Add routing for Google to WARP, restart the panel to take effect"
|
||||||
|
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) to WARP"
|
||||||
|
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) to WARP, restart the panel to take effect"
|
||||||
|
"xrayConfigNetflixWARP" = "Route Netflix to WARP"
|
||||||
|
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix to WARP, restart the panel to take effect"
|
||||||
|
"xrayConfigSpotifyWARP" = "Route Spotify to WARP"
|
||||||
|
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify to WARP, restart the panel to take effect"
|
||||||
|
"xrayConfigIRWARP" = "Route Iran Domains to WARP"
|
||||||
|
"xrayConfigIRWARPDesc" = "Add routing for Iran Domains to WARP. restart the panel to take effect"
|
||||||
"xrayConfigInbounds" = "Configuration of Inbounds"
|
"xrayConfigInbounds" = "Configuration of Inbounds"
|
||||||
"xrayConfigInboundsDesc" = "Change the configuration temlate to accept special clients, restart the panel to take effect"
|
"xrayConfigInboundsDesc" = "Change the configuration template to accept special clients, restart the panel to take effect"
|
||||||
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
||||||
"xrayConfigOutboundsDesc" = "Change the configuration temlate to define outgoing ways for this server, restart the panel to take effect"
|
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server, restart the panel to take effect"
|
||||||
"xrayConfigRoutings" = "Configuration of Routing rules"
|
"xrayConfigRoutings" = "Configuration of Routing rules"
|
||||||
"xrayConfigRoutingsDesc" = "Change the configuration temlate to define Routing rules for this server, restart the panel to take effect"
|
"xrayConfigRoutingsDesc" = "Change the configuration template to define Routing rules for this server, restart the panel to take effect"
|
||||||
"telegramBotEnable" = "Enable telegram bot"
|
"telegramBotEnable" = "Enable telegram bot"
|
||||||
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
||||||
"telegramToken" = "Telegram Token"
|
"telegramToken" = "Telegram Token"
|
||||||
@@ -243,6 +288,8 @@
|
|||||||
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
||||||
"tgNotifyBackup" = "Database backup"
|
"tgNotifyBackup" = "Database backup"
|
||||||
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
||||||
|
"sessionMaxAge" = "Session maximum age"
|
||||||
|
"sessionMaxAgeDesc" = "The time that you can stay login (unit: minute)"
|
||||||
"expireTimeDiff" = "Exhaustion time threshold"
|
"expireTimeDiff" = "Exhaustion time threshold"
|
||||||
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
||||||
"trafficDiff" = "Exhaustion traffic threshold"
|
"trafficDiff" = "Exhaustion traffic threshold"
|
||||||
@@ -251,6 +298,10 @@
|
|||||||
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
|
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
|
||||||
"timeZonee" = "Time Zone"
|
"timeZonee" = "Time Zone"
|
||||||
"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"
|
||||||
|
"loginSecurity" = "Login security"
|
||||||
|
"loginSecurityDesc" = "Toggle additional step in user login page"
|
||||||
|
"secretToken" = "Secret Token"
|
||||||
|
"secretTokenDesc" = "Copy this secret token and keep it in a safe place; without this you won't be able to login. This can not be recovered from x-ui command tool neither"
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.setting.toasts]
|
||||||
"modifySetting" = "Modify setting"
|
"modifySetting" = "Modify setting"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"install" = "نصب"
|
"install" = "نصب"
|
||||||
"clients" = "کاربران"
|
"clients" = "کاربران"
|
||||||
"usage" = "استفاده"
|
"usage" = "استفاده"
|
||||||
|
"secretToken" = "توکن امنیتی"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "وضعیت سیستم"
|
"dashboard" = "وضعیت سیستم"
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
"totalDownUp" = "جمع آپلود/دانلود"
|
"totalDownUp" = "جمع آپلود/دانلود"
|
||||||
"totalUsage" = "جمع کل"
|
"totalUsage" = "جمع کل"
|
||||||
"inboundCount" = "تعداد سرویس ها"
|
"inboundCount" = "تعداد سرویس ها"
|
||||||
"operate" = "عملیات"
|
"operate" = "فهرست"
|
||||||
"enable" = "فعال"
|
"enable" = "فعال"
|
||||||
"remark" = "نام"
|
"remark" = "نام"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
@@ -106,6 +107,7 @@
|
|||||||
"expireDate" = "تاریخ انقضا"
|
"expireDate" = "تاریخ انقضا"
|
||||||
"resetTraffic" = "ریست ترافیک"
|
"resetTraffic" = "ریست ترافیک"
|
||||||
"addInbound" = "اضافه کردن سرویس"
|
"addInbound" = "اضافه کردن سرویس"
|
||||||
|
"generalActions" = "عملیات کلی"
|
||||||
"addTo" = "اضافه کردن"
|
"addTo" = "اضافه کردن"
|
||||||
"revise" = "ویرایش"
|
"revise" = "ویرایش"
|
||||||
"modifyInbound" = "ویرایش سرویس"
|
"modifyInbound" = "ویرایش سرویس"
|
||||||
@@ -139,9 +141,15 @@
|
|||||||
"resetAllTraffic" = "ریست ترافیک کل سرویس ها"
|
"resetAllTraffic" = "ریست ترافیک کل سرویس ها"
|
||||||
"resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها"
|
"resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها"
|
||||||
"resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟"
|
"resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟"
|
||||||
|
"resetInboundClientTraffics" = "ریست ترافیک کاربران"
|
||||||
|
"resetInboundClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
||||||
|
"resetInboundClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
|
||||||
"resetAllClientTraffics" = "ریست ترافیک کاربران"
|
"resetAllClientTraffics" = "ریست ترافیک کاربران"
|
||||||
"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
||||||
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
|
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران را ریست کنید؟"
|
||||||
|
"delDepletedClients" = "حذف کاربران منقضی"
|
||||||
|
"delDepletedClientsTitle" = "حذف کاربران منقضی"
|
||||||
|
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
|
||||||
"IPLimit" = "محدودیت ای پی"
|
"IPLimit" = "محدودیت ای پی"
|
||||||
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
||||||
"Email" = "ایمیل"
|
"Email" = "ایمیل"
|
||||||
@@ -150,7 +158,7 @@
|
|||||||
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
||||||
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
||||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
"XTLSdec" = "هسته Xray باید 1.7.5 و کمتر باشد"
|
"XTLSdec" = "هسته Xray باید 1.7.5 باشد"
|
||||||
"Realitydec" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
"Realitydec" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -194,6 +202,8 @@
|
|||||||
"save" = "ذخیره"
|
"save" = "ذخیره"
|
||||||
"restartPanel" = "ریستارت پنل"
|
"restartPanel" = "ریستارت پنل"
|
||||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||||
|
"actions" = "عملیات ها"
|
||||||
|
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||||
"panelConfig" = "تنظیمات پنل"
|
"panelConfig" = "تنظیمات پنل"
|
||||||
"userSetting" = "تنظیمات مدیر"
|
"userSetting" = "تنظیمات مدیر"
|
||||||
"xrayConfiguration" = "تنظیمات Xray"
|
"xrayConfiguration" = "تنظیمات Xray"
|
||||||
@@ -213,18 +223,53 @@
|
|||||||
"currentPassword" = "رمز عبور فعلی"
|
"currentPassword" = "رمز عبور فعلی"
|
||||||
"newUsername" = "نام کاربری جدید"
|
"newUsername" = "نام کاربری جدید"
|
||||||
"newPassword" = "رمز عبور جدید"
|
"newPassword" = "رمز عبور جدید"
|
||||||
|
"basicTemplate" = "بخش پایه"
|
||||||
"advancedTemplate" = "بخش های پیشرفته الگو"
|
"advancedTemplate" = "بخش های پیشرفته الگو"
|
||||||
"completeTemplate" = "الگوی کامل تنظیمات ایکس ری"
|
"completeTemplate" = "الگوی کامل تنظیمات ایکس ری"
|
||||||
|
"generalConfigs" = "تنظیمات عمومی"
|
||||||
|
"generalConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند."
|
||||||
|
"countryConfigs" = "تنظیمات برای کشورها"
|
||||||
|
"countryConfigsDesc" = "این گزینه از اتصال کاربران به دامنه های کشوری خاص جلوگیری می کند."
|
||||||
|
"ipv4Configs" = "تنظیمات برای IPv4"
|
||||||
|
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن 4 به دامنه های هدف هدایت می شود."
|
||||||
|
"warpConfigs" = "تنظیمات برای WARP"
|
||||||
|
"warpConfigsDesc" = "هشدار: قبل از استفاده از این گزینه، WARP را در حالت پراکسی socks5 با دنبال کردن مراحل در GitHub پنل روی سرور خود نصب کنید. WARP ترافیک را از طریق سرورهای Cloudflare به وب سایت ها هدایت می کند."
|
||||||
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
||||||
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
||||||
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigPrivateIp" = "جلوگیری از اتصال آی پی های نامعتبر"
|
"xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
|
||||||
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های نامعتبر و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigIRIp" = "جلوگیری از اتصال آی پی های ایران"
|
"xrayConfigAds" = "مسدود کردن تبلیغات"
|
||||||
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigIRdomain" = "جلوگیری از اتصال دامنه های ایران"
|
"xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
|
||||||
"xrayConfigIRdomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
||||||
|
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
||||||
|
"xrayConfigIRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigChinaIp" = "جلوگیری از اتصال آیپی های چین"
|
||||||
|
"xrayConfigChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigChinaDomain" = "جلوگیری از اتصال دامنه های چین"
|
||||||
|
"xrayConfigChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigRussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
|
||||||
|
"xrayConfigRussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigRussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
|
||||||
|
"xrayConfigRussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigGoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
|
||||||
|
"xrayConfigGoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigNetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
|
||||||
|
"xrayConfigNetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigGoogleWARP" = "مسیردهی گوگل به WARP"
|
||||||
|
"xrayConfigGoogleWARPDesc" = "مسیردهی جدید برای اتصال به گوگل به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigOpenAIWARP" = "مسیردهی OpenAI (ChatGPT) به WARP"
|
||||||
|
"xrayConfigOpenAIWARPDesc" = "مسیردهی جدید برای اتصال به OpenAI (ChatGPT) به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigNetflixWARP" = "مسیردهی نتفلیکس به WARP"
|
||||||
|
"xrayConfigNetflixWARPDesc" = "مسیردهی جدید برای اتصال به نتفلیکس به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigSpotifyWARP" = "مسیردهی اسپاتیفای به WARP"
|
||||||
|
"xrayConfigSpotifyWARPDesc" = "مسیردهی جدید برای اتصال به اسپاتیفای به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigIRWARP" = "مسیردهی دامنه های ایران به WARP"
|
||||||
|
"xrayConfigIRWARPDesc" = "مسیردهی جدید برای اتصال به دامنه های ایران به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigInbounds" = "تنظیمات ورودی"
|
"xrayConfigInbounds" = "تنظیمات ورودی"
|
||||||
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
||||||
@@ -241,6 +286,8 @@
|
|||||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||||
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||||
|
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
||||||
|
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
||||||
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
||||||
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
||||||
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
||||||
@@ -249,6 +296,10 @@
|
|||||||
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
||||||
"timeZonee" = "منظقه زمانی"
|
"timeZonee" = "منظقه زمانی"
|
||||||
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
||||||
|
"loginSecurity" = "لاگین ایمن"
|
||||||
|
"loginSecurityDesc" = "افزودن یک مرحله دیگر به فرآیند لاگین"
|
||||||
|
"secretToken" = "توکن امنیتی"
|
||||||
|
"secretTokenDesc" = "این کد امنیتی را نزد خود در این جای امن نگه داری، بدون این کد امکان ورود به پنل را نخواهید داشت. امکان بازیابی آن وجود ندارد!"
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.setting.toasts]
|
||||||
"modifySetting" = "ویرایش تنظیمات"
|
"modifySetting" = "ویرایش تنظیمات"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"install" = "安装"
|
"install" = "安装"
|
||||||
"clients" = "客户端"
|
"clients" = "客户端"
|
||||||
"usage" = "用法"
|
"usage" = "用法"
|
||||||
|
"secretToken" = "秘密令牌"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "系统状态"
|
"dashboard" = "系统状态"
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
"totalDownUp" = "总上传 / 下载"
|
"totalDownUp" = "总上传 / 下载"
|
||||||
"totalUsage" = "总用量"
|
"totalUsage" = "总用量"
|
||||||
"inboundCount" = "入站数量"
|
"inboundCount" = "入站数量"
|
||||||
"operate" = "操作"
|
"operate" = "菜单"
|
||||||
"enable" = "启用"
|
"enable" = "启用"
|
||||||
"remark" = "备注"
|
"remark" = "备注"
|
||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
@@ -106,6 +107,7 @@
|
|||||||
"expireDate" = "到期时间"
|
"expireDate" = "到期时间"
|
||||||
"resetTraffic" = "重置流量"
|
"resetTraffic" = "重置流量"
|
||||||
"addInbound" = "添加入"
|
"addInbound" = "添加入"
|
||||||
|
"generalActions" = "通用操作"
|
||||||
"addTo" = "添加"
|
"addTo" = "添加"
|
||||||
"revise" = "修改"
|
"revise" = "修改"
|
||||||
"modifyInbound" = "修改入站"
|
"modifyInbound" = "修改入站"
|
||||||
@@ -139,9 +141,15 @@
|
|||||||
"resetAllTraffic" = "重置所有入站流量"
|
"resetAllTraffic" = "重置所有入站流量"
|
||||||
"resetAllTrafficTitle" = "重置所有入站流量"
|
"resetAllTrafficTitle" = "重置所有入站流量"
|
||||||
"resetAllTrafficContent" = "您确定要重置所有入站流量吗?"
|
"resetAllTrafficContent" = "您确定要重置所有入站流量吗?"
|
||||||
"resetAllClientTraffics" = "重置客户端流量"
|
"resetInboundClientTraffics" = "重置客户端流量"
|
||||||
|
"resetInboundClientTrafficTitle" = "重置所有客户端流量"
|
||||||
|
"resetInboundClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
|
||||||
|
"resetAllClientTraffics" = "重置所有客户端流量"
|
||||||
"resetAllClientTrafficTitle" = "重置所有客户端流量"
|
"resetAllClientTrafficTitle" = "重置所有客户端流量"
|
||||||
"resetAllClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
|
"resetAllClientTrafficContent" = "你确定要重置所有客户端的所有流量吗?"
|
||||||
|
"delDepletedClients" = "删除耗尽的客户端"
|
||||||
|
"delDepletedClientsTitle" = "删除耗尽的客户"
|
||||||
|
"delDepletedClientsContent" = "你确定要删除所有耗尽的客户端吗?"
|
||||||
"IPLimit" = "IP限制"
|
"IPLimit" = "IP限制"
|
||||||
"IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
|
"IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
|
||||||
"Email" = "电子邮件"
|
"Email" = "电子邮件"
|
||||||
@@ -150,7 +158,7 @@
|
|||||||
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
||||||
"IPLimitlogclear" = "清除日志"
|
"IPLimitlogclear" = "清除日志"
|
||||||
"setDefaultCert" = "从面板设置证书"
|
"setDefaultCert" = "从面板设置证书"
|
||||||
"XTLSdec" = "Xray核心需要1.7.5及以下版本"
|
"XTLSdec" = "Xray核心需要1.7.5"
|
||||||
"Realitydec" = "Xray核心需要1.8.0及以上版本"
|
"Realitydec" = "Xray核心需要1.8.0及以上版本"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -194,6 +202,8 @@
|
|||||||
"save" = "保存配置"
|
"save" = "保存配置"
|
||||||
"restartPanel" = "重启面板"
|
"restartPanel" = "重启面板"
|
||||||
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
||||||
|
"actions" = "动作"
|
||||||
|
"resetDefaultConfig" = "重置为默认配置"
|
||||||
"panelConfig" = "面板配置"
|
"panelConfig" = "面板配置"
|
||||||
"userSetting" = "用户设置"
|
"userSetting" = "用户设置"
|
||||||
"xrayConfiguration" = "xray 相关设置"
|
"xrayConfiguration" = "xray 相关设置"
|
||||||
@@ -213,18 +223,53 @@
|
|||||||
"currentPassword" = "原密码"
|
"currentPassword" = "原密码"
|
||||||
"newUsername" = "新用户名"
|
"newUsername" = "新用户名"
|
||||||
"newPassword" = "新密码"
|
"newPassword" = "新密码"
|
||||||
|
"basicTemplate" = "基本模板"
|
||||||
"advancedTemplate" = "高级模板部件"
|
"advancedTemplate" = "高级模板部件"
|
||||||
"completeTemplate" = "Xray 配置的完整模板"
|
"completeTemplate" = "Xray 配置的完整模板"
|
||||||
|
"generalConfigs" = "一般配置"
|
||||||
|
"generalConfigsDesc" = "此选项将阻止用户连接到特定协议和网站。"
|
||||||
|
"countryConfigs" = "国家配置"
|
||||||
|
"countryConfigsDesc" = "此选项将阻止用户连接到特定国家/地区的域。"
|
||||||
|
"ipv4Configs" = "IPv4 配置"
|
||||||
|
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域。"
|
||||||
|
"warpConfigs" = "WARP 配置"
|
||||||
|
"warpConfigsDesc" = "警告:在使用此选项之前,请按照面板 GitHub 上的步骤在您的服务器上以 socks5 代理模式安装 WARP。 WARP 将通过 Cloudflare 服务器将流量路由到网站。"
|
||||||
"xrayConfigTemplate" = "xray 配置模板"
|
"xrayConfigTemplate" = "xray 配置模板"
|
||||||
"xrayConfigTemplateDesc" = "以该模型为基础生成最终的xray配置文件,重新启动面板生成效率"
|
"xrayConfigTemplateDesc" = "以该模型为基础生成最终的xray配置文件,重新启动面板生成效率"
|
||||||
"xrayConfigTorrent" = "禁止使用 bittorrent"
|
"xrayConfigTorrent" = "禁止使用 bittorrent"
|
||||||
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent,重启面板生效"
|
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent,重启面板生效"
|
||||||
"xrayConfigPrivateIp" = "禁止私人 ip 范围连接"
|
"xrayConfigPrivateIp" = "禁止私人 IP 范围连接"
|
||||||
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效"
|
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效"
|
||||||
|
"xrayConfigAds" = "屏蔽广告"
|
||||||
|
"xrayConfigAdsDesc" = "修改配置模板屏蔽广告,重启面板生效"
|
||||||
|
"xrayConfigPorn" = "禁止色情网站连接"
|
||||||
|
"xrayConfigPornDesc" = "更改配置模板避免连接色情网站,重启面板生效"
|
||||||
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
|
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
|
||||||
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP范围,重启面板生效"
|
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段,重启面板生效"
|
||||||
"xrayConfigIRdomain" = "禁止伊朗域连接"
|
"xrayConfigIRDomain" = "禁止伊朗域连接"
|
||||||
"xrayConfigIRdomainDesc" = "修改配置模板避免连接伊朗域名,重启面板生效"
|
"xrayConfigIRDomainDesc" = "更改配置模板避免连接伊朗域名,重启面板生效"
|
||||||
|
"xrayConfigChinaIp" = "禁止中国 IP 范围连接"
|
||||||
|
"xrayConfigChinaIpDesc" = "修改配置模板避免连接中国IP段,重启面板生效"
|
||||||
|
"xrayConfigChinaDomain" = "禁止中国域名连接"
|
||||||
|
"xrayConfigChinaDomainDesc" = "更改配置模板避免连接中国域,重启面板生效"
|
||||||
|
"xrayConfigRussiaIp" = "禁止俄罗斯 IP 范围连接"
|
||||||
|
"xrayConfigRussiaIpDesc" = "修改配置模板避免连接俄罗斯IP范围,重启面板生效"
|
||||||
|
"xrayConfigRussiaDomain" = "禁止俄罗斯域连接"
|
||||||
|
"xrayConfigRussiaDomainDesc" = "更改配置模板避免连接俄罗斯域,重启面板生效"
|
||||||
|
"xrayConfigGoogleIPv4" = "为谷歌使用 IPv4"
|
||||||
|
"xrayConfigGoogleIPv4Desc" = "添加谷歌连接IPv4的路由,重启面板生效"
|
||||||
|
"xrayConfigNetflixIPv4" = "为 Netflix 使用 IPv4"
|
||||||
|
"xrayConfigNetflixIPv4Desc" = "添加Netflix连接IPv4的路由,重启面板生效"
|
||||||
|
"xrayConfigGoogleWARP" = "将谷歌路由到 WARP"
|
||||||
|
"xrayConfigGoogleWARPDesc" = "为谷歌添加路由到WARP,重启面板生效"
|
||||||
|
"xrayConfigOpenAIWARP" = "将 OpenAI (ChatGPT) 路由到 WARP"
|
||||||
|
"xrayConfigOpenAIWARPDesc" = "将OpenAI(ChatGPT)路由添加到WARP,重启面板生效"
|
||||||
|
"xrayConfigNetflixWARP" = "将 Netflix 路由到 WARP"
|
||||||
|
"xrayConfigNetflixWARPDesc" = "为Netflix添加路由到WARP,重启面板生效"
|
||||||
|
"xrayConfigSpotifyWARP" = "将 Spotify 路由到 WARP"
|
||||||
|
"xrayConfigSpotifyWARPDesc" = "为Spotify添加路由到WARP,重启面板生效"
|
||||||
|
"xrayConfigIRWARP" = "将伊朗域名路由到 WARP"
|
||||||
|
"xrayConfigIRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
|
||||||
"xrayConfigInbounds" = "入站配置"
|
"xrayConfigInbounds" = "入站配置"
|
||||||
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效"
|
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效"
|
||||||
"xrayConfigOutbounds" = "出站配置"
|
"xrayConfigOutbounds" = "出站配置"
|
||||||
@@ -241,6 +286,8 @@
|
|||||||
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
||||||
"tgNotifyBackup" = "数据库备份"
|
"tgNotifyBackup" = "数据库备份"
|
||||||
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
||||||
|
"sessionMaxAge" = "会话最大年龄"
|
||||||
|
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)"
|
||||||
"expireTimeDiff" = "耗尽时间阈值"
|
"expireTimeDiff" = "耗尽时间阈值"
|
||||||
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
||||||
"trafficDiff" = "耗尽流量阈值"
|
"trafficDiff" = "耗尽流量阈值"
|
||||||
@@ -249,6 +296,10 @@
|
|||||||
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
||||||
"timeZonee" = "时区"
|
"timeZonee" = "时区"
|
||||||
"timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效"
|
"timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效"
|
||||||
|
"loginSecurity" = "登录安全"
|
||||||
|
"loginSecurityDesc" = "在用户登录页面中切换附加步骤"
|
||||||
|
"secretToken" = "秘密令牌"
|
||||||
|
"secretTokenDesc" = "复制此秘密令牌并将其保存在安全的地方;没有这个你将无法登录。这也无法从 x-ui 命令工具中恢复"
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.setting.toasts]
|
||||||
"modifySetting" = "修改设置"
|
"modifySetting" = "修改设置"
|
||||||
|
|||||||
235
x-ui.sh
235
x-ui.sh
@@ -17,6 +17,7 @@ function LOGE() {
|
|||||||
function LOGI() {
|
function LOGI() {
|
||||||
echo -e "${green}[INF] $* ${plain}"
|
echo -e "${green}[INF] $* ${plain}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
@@ -34,7 +35,6 @@ fi
|
|||||||
|
|
||||||
echo "The OS release is: $release"
|
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=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
||||||
|
|
||||||
@@ -44,24 +44,21 @@ if [[ "${release}" == "centos" ]]; then
|
|||||||
fi
|
fi
|
||||||
elif [[ "${release}" == "ubuntu" ]]; then
|
elif [[ "${release}" == "ubuntu" ]]; then
|
||||||
if [[ ${os_version} -lt 20 ]]; then
|
if [[ ${os_version} -lt 20 ]]; then
|
||||||
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [[ "${release}" == "fedora" ]]; then
|
elif [[ "${release}" == "fedora" ]]; then
|
||||||
if [[ ${os_version} -lt 36 ]]; then
|
if [[ ${os_version} -lt 36 ]]; then
|
||||||
echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1
|
echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [[ "${release}" == "debian" ]]; then
|
elif [[ "${release}" == "debian" ]]; then
|
||||||
if [[ ${os_version} -lt 10 ]]; then
|
if [[ ${os_version} -lt 10 ]]; then
|
||||||
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
if [[ $# > 1 ]]; then
|
if [[ $# > 1 ]]; then
|
||||||
echo && read -p "$1 [Default$2]: " temp
|
echo && read -p "$1 [Default $2]: " temp
|
||||||
if [[ x"${temp}" == x"" ]]; then
|
if [[ x"${temp}" == x"" ]]; then
|
||||||
temp=$2
|
temp=$2
|
||||||
fi
|
fi
|
||||||
@@ -133,7 +130,7 @@ uninstall() {
|
|||||||
rm /usr/local/x-ui/ -rf
|
rm /usr/local/x-ui/ -rf
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "Uninstalled Successfully,If you want to remove this script,then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
|
echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
@@ -142,20 +139,28 @@ uninstall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reset_user() {
|
reset_user() {
|
||||||
confirm "Reset your username and password to admin?" "n"
|
confirm "Are you sure to reset the username and password of the panel?" "n"
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
show_menu
|
show_menu
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
/usr/local/x-ui/x-ui setting -username admin -password admin
|
read -rp "Please set the login username [default is a random username]: " config_account
|
||||||
echo -e "Username and password have been reset to ${green}admin${plain},Please restart the panel now."
|
[[ -z $config_account ]] && config_account=$(date +%s%N | md5sum | cut -c 1-8)
|
||||||
|
read -rp "Please set the login password [default is a random password]: " config_password
|
||||||
|
[[ -z $config_password ]] && config_password=$(date +%s%N | md5sum | cut -c 1-8)
|
||||||
|
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} >/dev/null 2>&1
|
||||||
|
/usr/local/x-ui/x-ui setting -remove_secret >/dev/null 2>&1
|
||||||
|
echo -e "Panel login username has been reset to: ${green} ${config_account} ${plain}"
|
||||||
|
echo -e "Panel login password has been reset to: ${green} ${config_password} ${plain}"
|
||||||
|
echo -e "${yellow} Panel login secret token disabled ${plain}"
|
||||||
|
echo -e "${green} Please use the new login username and password to access the X-UI panel. Also remember them! ${plain}"
|
||||||
confirm_restart
|
confirm_restart
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_config() {
|
reset_config() {
|
||||||
confirm "Are you sure you want to reset all panel settings,Account data will not be lost,Username and password will not change" "n"
|
confirm "Are you sure you want to reset all panel settings, Account data will not be lost, Username and password will not change" "n"
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
show_menu
|
show_menu
|
||||||
@@ -163,14 +168,14 @@ reset_config() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
/usr/local/x-ui/x-ui setting -reset
|
/usr/local/x-ui/x-ui setting -reset
|
||||||
echo -e "All panel settings have been reset to default,Please restart the panel now,and use the default ${green}2053${plain} Port to Access the web Panel"
|
echo -e "All panel settings have been reset to default, Please restart the panel now, and use the default ${green}2053${plain} Port to Access the web Panel"
|
||||||
confirm_restart
|
confirm_restart
|
||||||
}
|
}
|
||||||
|
|
||||||
check_config() {
|
check_config() {
|
||||||
info=$(/usr/local/x-ui/x-ui setting -show true)
|
info=$(/usr/local/x-ui/x-ui setting -show true)
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
LOGE "get current settings error,please check logs"
|
LOGE "get current settings error, please check logs"
|
||||||
show_menu
|
show_menu
|
||||||
fi
|
fi
|
||||||
LOGI "${info}"
|
LOGI "${info}"
|
||||||
@@ -183,7 +188,7 @@ set_port() {
|
|||||||
before_show_menu
|
before_show_menu
|
||||||
else
|
else
|
||||||
/usr/local/x-ui/x-ui setting -port ${port}
|
/usr/local/x-ui/x-ui setting -port ${port}
|
||||||
echo -e "The port is set,Please restart the panel now,and use the new port ${green}${port}${plain} to access web panel"
|
echo -e "The port is set, Please restart the panel now, and use the new port ${green}${port}${plain} to access web panel"
|
||||||
confirm_restart
|
confirm_restart
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -192,7 +197,7 @@ start() {
|
|||||||
check_status
|
check_status
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGI "Panel is running,No need to start again,If you need to restart, please select restart"
|
LOGI "Panel is running, No need to start again, If you need to restart, please select restart"
|
||||||
else
|
else
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
sleep 2
|
sleep 2
|
||||||
@@ -200,7 +205,7 @@ start() {
|
|||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
LOGI "x-ui Started Successfully"
|
LOGI "x-ui Started Successfully"
|
||||||
else
|
else
|
||||||
LOGE "panel Failed to start,Probably because it takes longer than two seconds to start,Please check the log information later"
|
LOGE "panel Failed to start, Probably because it takes longer than two seconds to start, Please check the log information later"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -213,7 +218,7 @@ stop() {
|
|||||||
check_status
|
check_status
|
||||||
if [[ $? == 1 ]]; then
|
if [[ $? == 1 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGI "Panel stopped,No need to stop again!"
|
LOGI "Panel stopped, No need to stop again!"
|
||||||
else
|
else
|
||||||
systemctl stop x-ui
|
systemctl stop x-ui
|
||||||
sleep 2
|
sleep 2
|
||||||
@@ -221,7 +226,7 @@ stop() {
|
|||||||
if [[ $? == 1 ]]; then
|
if [[ $? == 1 ]]; then
|
||||||
LOGI "x-ui and xray stopped successfully"
|
LOGI "x-ui and xray stopped successfully"
|
||||||
else
|
else
|
||||||
LOGE "Panel stop failed,Probably because the stop time exceeds two seconds,Please check the log information later"
|
LOGE "Panel stop failed, Probably because the stop time exceeds two seconds, Please check the log information later"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -237,7 +242,7 @@ restart() {
|
|||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
LOGI "x-ui and xray Restarted successfully"
|
LOGI "x-ui and xray Restarted successfully"
|
||||||
else
|
else
|
||||||
LOGE "Panel restart failed,Probably because it takes longer than two seconds to start,Please check the log information later"
|
LOGE "Panel restart failed, Probably because it takes longer than two seconds to start, Please check the log information later"
|
||||||
fi
|
fi
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
before_show_menu
|
before_show_menu
|
||||||
@@ -285,51 +290,49 @@ show_log() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enable_bbr() {
|
enable_bbr() {
|
||||||
|
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
|
||||||
|
echo -e "${green}BBR is already enabled!${plain}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
|
# Check the OS and install necessary packages
|
||||||
echo -e "${green}BBR is already enabled!${plain}"
|
if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
|
||||||
exit 0
|
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
|
||||||
fi
|
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "debian" ]]; then
|
||||||
|
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
|
||||||
|
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then
|
||||||
|
sudo dnf -y update && sudo dnf -y install ca-certificates
|
||||||
|
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then
|
||||||
|
sudo yum -y update && sudo yum -y install ca-certificates
|
||||||
|
else
|
||||||
|
echo "Unsupported operating system. Please check the script and install the necessary packages manually."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Check the OS and install necessary packages
|
# Enable BBR
|
||||||
if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
|
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
|
||||||
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
|
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
|
||||||
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "debian" ]]; then
|
|
||||||
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
|
|
||||||
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then
|
|
||||||
sudo dnf -y update && sudo dnf -y install ca-certificates
|
|
||||||
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then
|
|
||||||
sudo yum -y update && sudo yum -y install ca-certificates
|
|
||||||
else
|
|
||||||
echo "Unsupported operating system. Please check the script and install the necessary packages manually."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Enable BBR
|
# Apply changes
|
||||||
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
|
sudo sysctl -p
|
||||||
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
|
|
||||||
|
|
||||||
# Apply changes
|
|
||||||
sudo sysctl -p
|
|
||||||
|
|
||||||
# Verify that BBR is enabled
|
|
||||||
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
|
|
||||||
echo -e "${green}BBR has been enabled successfully.${plain}"
|
|
||||||
else
|
|
||||||
echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
# Verify that BBR is enabled
|
||||||
|
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
|
||||||
|
echo -e "${green}BBR has been enabled successfully.${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
update_shell() {
|
update_shell() {
|
||||||
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh
|
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGE "Failed to download script,Please check whether the machine can connect Github"
|
LOGE "Failed to download script, Please check whether the machine can connect Github"
|
||||||
before_show_menu
|
before_show_menu
|
||||||
else
|
else
|
||||||
chmod +x /usr/bin/x-ui
|
chmod +x /usr/bin/x-ui
|
||||||
LOGI "Upgrade script succeeded,Please rerun the script" && exit 0
|
LOGI "Upgrade script succeeded, Please rerun the script" && exit 0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +362,7 @@ check_uninstall() {
|
|||||||
check_status
|
check_status
|
||||||
if [[ $? != 2 ]]; then
|
if [[ $? != 2 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGE "Panel installed,Please do not reinstall"
|
LOGE "Panel installed, Please do not reinstall"
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
before_show_menu
|
before_show_menu
|
||||||
fi
|
fi
|
||||||
@@ -455,69 +458,76 @@ ssl_cert_issue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open_ports() {
|
open_ports() {
|
||||||
if ! command -v ufw &> /dev/null
|
if ! command -v ufw &> /dev/null
|
||||||
then
|
then
|
||||||
echo "ufw firewall is not installed. Installing now..."
|
echo "ufw firewall is not installed. Installing now..."
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y ufw
|
sudo apt-get install -y ufw
|
||||||
else
|
|
||||||
echo "ufw firewall is already installed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if the firewall is inactive
|
|
||||||
if sudo ufw status | grep -q "Status: active"; then
|
|
||||||
echo "firewall is already active"
|
|
||||||
else
|
|
||||||
# Open the necessary ports
|
|
||||||
sudo ufw allow ssh
|
|
||||||
sudo ufw allow http
|
|
||||||
sudo ufw allow https
|
|
||||||
sudo ufw allow 2053/tcp
|
|
||||||
|
|
||||||
# Enable the firewall
|
|
||||||
sudo ufw --force enable
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Prompt the user to enter a list of ports
|
|
||||||
read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports
|
|
||||||
|
|
||||||
# Check if the input is valid
|
|
||||||
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
|
|
||||||
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2; exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Open the specified ports using ufw
|
|
||||||
IFS=',' read -ra PORT_LIST <<< "$ports"
|
|
||||||
for port in "${PORT_LIST[@]}"; do
|
|
||||||
if [[ $port == *-* ]]; then
|
|
||||||
# Split the range into start and end ports
|
|
||||||
start_port=$(echo $port | cut -d'-' -f1)
|
|
||||||
end_port=$(echo $port | cut -d'-' -f2)
|
|
||||||
# Loop through the range and open each port
|
|
||||||
for ((i=start_port; i<=end_port; i++)); do
|
|
||||||
sudo ufw allow $i
|
|
||||||
done
|
|
||||||
else
|
else
|
||||||
sudo ufw allow "$port"
|
echo "ufw firewall is already installed"
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
# Confirm that the ports are open
|
# Check if the firewall is inactive
|
||||||
sudo ufw status | grep $ports
|
if sudo ufw status | grep -q "Status: active"; then
|
||||||
|
echo "firewall is already active"
|
||||||
|
else
|
||||||
|
# Open the necessary ports
|
||||||
|
sudo ufw allow ssh
|
||||||
|
sudo ufw allow http
|
||||||
|
sudo ufw allow https
|
||||||
|
sudo ufw allow 2053/tcp
|
||||||
|
|
||||||
|
# Enable the firewall
|
||||||
|
sudo ufw --force enable
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prompt the user to enter a list of ports
|
||||||
|
read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports
|
||||||
|
|
||||||
|
# Check if the input is valid
|
||||||
|
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
|
||||||
|
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Open the specified ports using ufw
|
||||||
|
IFS=',' read -ra PORT_LIST <<< "$ports"
|
||||||
|
for port in "${PORT_LIST[@]}"; do
|
||||||
|
if [[ $port == *-* ]]; then
|
||||||
|
# Split the range into start and end ports
|
||||||
|
start_port=$(echo $port | cut -d'-' -f1)
|
||||||
|
end_port=$(echo $port | cut -d'-' -f2)
|
||||||
|
# Loop through the range and open each port
|
||||||
|
for ((i=start_port; i<=end_port; i++)); do
|
||||||
|
sudo ufw allow $i
|
||||||
|
done
|
||||||
|
else
|
||||||
|
sudo ufw allow "$port"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Confirm that the ports are open
|
||||||
|
sudo ufw status | grep $ports
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_geo() {
|
||||||
|
local defaultBinFolder="/usr/local/x-ui/bin"
|
||||||
|
read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
|
||||||
|
binFolder=${binFolder:-${defaultBinFolder}}
|
||||||
|
if [[ ! -d ${binFolder} ]]; then
|
||||||
|
LOGE "Folder ${binFolder} not exists!"
|
||||||
|
LOGI "making bin folder: ${binFolder}..."
|
||||||
|
mkdir -p ${binFolder}
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
update_geo(){
|
|
||||||
systemctl stop x-ui
|
systemctl stop x-ui
|
||||||
cd /usr/local/x-ui/bin
|
cd ${binFolder}
|
||||||
rm -f geoip.dat geosite.dat iran.dat
|
rm -f geoip.dat geosite.dat iran.dat
|
||||||
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget -N https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
wget -N https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
echo -e "${green}Geosite and Geoip have been updated successfully!${plain}"
|
echo -e "${green}Geosite.dat + Geoip.dat + Iran.dat have been updated successfully in bin folder '${binfolder}'!${plain}"
|
||||||
before_show_menu
|
before_show_menu
|
||||||
}
|
}
|
||||||
|
|
||||||
install_acme() {
|
install_acme() {
|
||||||
@@ -714,10 +724,11 @@ ssl_cert_issue_by_cloudflare() {
|
|||||||
show_menu
|
show_menu
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
google_recaptcha() {
|
|
||||||
curl -O https://raw.githubusercontent.com/jinwyp/one_click_script/master/install_kernel.sh && chmod +x ./install_kernel.sh && ./install_kernel.sh
|
warp_fixchatgpt() {
|
||||||
echo ""
|
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
|
||||||
before_show_menu
|
echo ""
|
||||||
|
before_show_menu
|
||||||
}
|
}
|
||||||
|
|
||||||
run_speedtest() {
|
run_speedtest() {
|
||||||
@@ -778,7 +789,7 @@ show_menu() {
|
|||||||
${green}2.${plain} Update x-ui
|
${green}2.${plain} Update x-ui
|
||||||
${green}3.${plain} Uninstall x-ui
|
${green}3.${plain} Uninstall x-ui
|
||||||
————————————————
|
————————————————
|
||||||
${green}4.${plain} Reset Username And Password
|
${green}4.${plain} Reset Username & Password & Secret Token
|
||||||
${green}5.${plain} Reset Panel Settings
|
${green}5.${plain} Reset Panel Settings
|
||||||
${green}6.${plain} Change Panel Port
|
${green}6.${plain} Change Panel Port
|
||||||
${green}7.${plain} View Current Panel Settings
|
${green}7.${plain} View Current Panel Settings
|
||||||
@@ -790,13 +801,13 @@ show_menu() {
|
|||||||
${green}12.${plain} Check x-ui Logs
|
${green}12.${plain} Check x-ui Logs
|
||||||
————————————————
|
————————————————
|
||||||
${green}13.${plain} Enable x-ui On System Startup
|
${green}13.${plain} Enable x-ui On System Startup
|
||||||
${green}14.${plain} Disabel x-ui On System Startup
|
${green}14.${plain} Disable x-ui On System Startup
|
||||||
————————————————
|
————————————————
|
||||||
${green}15.${plain} Enable BBR
|
${green}15.${plain} Enable BBR
|
||||||
${green}16.${plain} Apply for an SSL Certificate
|
${green}16.${plain} Apply for an SSL Certificate
|
||||||
${green}17.${plain} Update Geo Files
|
${green}17.${plain} Update Geo Files
|
||||||
${green}18.${plain} Active Firewall and open ports
|
${green}18.${plain} Active Firewall and open ports
|
||||||
${green}19.${plain} Fixing Google reCAPTCHA
|
${green}19.${plain} Install WARP
|
||||||
${green}20.${plain} Speedtest by Ookla
|
${green}20.${plain} Speedtest by Ookla
|
||||||
"
|
"
|
||||||
show_status
|
show_status
|
||||||
@@ -861,7 +872,7 @@ show_menu() {
|
|||||||
open_ports
|
open_ports
|
||||||
;;
|
;;
|
||||||
19)
|
19)
|
||||||
google_recaptcha
|
warp_fixchatgpt
|
||||||
;;
|
;;
|
||||||
20)
|
20)
|
||||||
run_speedtest
|
run_speedtest
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"x-ui/config"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
|
||||||
"github.com/Workiva/go-datastructures/queue"
|
"github.com/Workiva/go-datastructures/queue"
|
||||||
@@ -29,19 +30,27 @@ func GetBinaryName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetBinaryPath() string {
|
func GetBinaryPath() string {
|
||||||
return "bin/" + GetBinaryName()
|
return config.GetBinFolderPath() + "/" + GetBinaryName()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigPath() string {
|
func GetConfigPath() string {
|
||||||
return "bin/config.json"
|
return config.GetBinFolderPath() + "/config.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeositePath() string {
|
func GetGeositePath() string {
|
||||||
return "bin/geosite.dat"
|
return config.GetBinFolderPath() + "/geosite.dat"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeoipPath() string {
|
func GetGeoipPath() string {
|
||||||
return "bin/geoip.dat"
|
return config.GetBinFolderPath() + "/geoip.dat"
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIranPath() string {
|
||||||
|
return config.GetBinFolderPath() + "/iran.dat"
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBlockedIPsPath() string {
|
||||||
|
return config.GetBinFolderPath() + "/blockedIPs"
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProcess(p *Process) {
|
func stopProcess(p *Process) {
|
||||||
@@ -162,7 +171,7 @@ func (p *process) Start() (err error) {
|
|||||||
return common.NewErrorf("Failed to write configuration file: %v", err)
|
return common.NewErrorf("Failed to write configuration file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", "./bin/blockedIPs")
|
cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", GetBlockedIPsPath())
|
||||||
p.cmd = cmd
|
p.cmd = cmd
|
||||||
|
|
||||||
stdReader, err := cmd.StdoutPipe()
|
stdReader, err := cmd.StdoutPipe()
|
||||||
|
|||||||
Reference in New Issue
Block a user