mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-16 14:08:17 +00:00
Compare commits
48 Commits
0.5.0
...
1.0.0_beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
772a1b341e | ||
|
|
5923c765a9 | ||
|
|
d7074cc3c9 | ||
|
|
f4f8c0d6df | ||
|
|
0c755be648 | ||
|
|
c8dc4a96b4 | ||
|
|
8d9f6ccc11 | ||
|
|
3d7a8e372e | ||
|
|
2fe4f1ad07 | ||
|
|
04095b6ec4 | ||
|
|
6d404e4b27 | ||
|
|
f9f8431dd2 | ||
|
|
f996b9ee2b | ||
|
|
a3e1ee1f35 | ||
|
|
e16391ad05 | ||
|
|
48b543becb | ||
|
|
80f052697c | ||
|
|
fb15248030 | ||
|
|
6a18ba48aa | ||
|
|
c13b7b2a18 | ||
|
|
838bdaf314 | ||
|
|
f6f0cf3657 | ||
|
|
dd5e9a97a6 | ||
|
|
edfbb91dff | ||
|
|
eb239b33af | ||
|
|
4b9940fb06 | ||
|
|
98d5e960a4 | ||
|
|
61fd029e28 | ||
|
|
2be3df59be | ||
|
|
7cf192e179 | ||
|
|
3a002c886d | ||
|
|
4b05c333ca | ||
|
|
bfc5b37bb1 | ||
|
|
07089455b5 | ||
|
|
7add969312 | ||
|
|
501f778c9e | ||
|
|
ac52340c51 | ||
|
|
f5f90d81a3 | ||
|
|
e6c23caa1d | ||
|
|
bf3a64c77d | ||
|
|
e2e88d8652 | ||
|
|
91b453fff7 | ||
|
|
9e955df76a | ||
|
|
58ca5f7a51 | ||
|
|
4f469f9ad5 | ||
|
|
1969120c94 | ||
|
|
cdbf742b66 | ||
|
|
b834ac5d93 |
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
mv xui-release x-ui
|
||||
mkdir bin
|
||||
cd bin
|
||||
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-64.zip
|
||||
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
mv xui-release x-ui
|
||||
mkdir bin
|
||||
cd bin
|
||||
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
||||
wget https://github.com/xtls/xray-core/releases/download/v1.8.1/Xray-linux-arm64-v8a.zip
|
||||
unzip Xray-linux-arm64-v8a.zip
|
||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
mv xui-release x-ui
|
||||
mkdir bin
|
||||
cd bin
|
||||
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-s390x.zip
|
||||
wget https://github.com/xtls/xray-core/releases/download/v1.8.1/Xray-linux-s390x.zip
|
||||
unzip Xray-linux-s390x.zip
|
||||
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
|
||||
@@ -11,6 +11,12 @@ ENV TZ=Asia/Tehran
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add ca-certificates tzdata && mkdir bin
|
||||
|
||||
# Download latest rule files
|
||||
ADD https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat \
|
||||
https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat \
|
||||
bin/
|
||||
|
||||
COPY --from=builder /app/main /app/x-ui
|
||||
VOLUME [ "/etc/x-ui" ]
|
||||
CMD [ "./x-ui" ]
|
||||
97
README.md
97
README.md
@@ -1,27 +1,29 @@
|
||||
# x-ui
|
||||

|
||||
|
||||

|
||||

|
||||
[](https://goreportcard.com/report/github.com/alireza0/x-ui)
|
||||
[](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)
|
||||
[](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**
|
||||
|
||||
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
||||
|
||||
| Features | Enable? |
|
||||
| ------------- |:-------------:|
|
||||
| Multi-lang | :heavy_check_mark: |
|
||||
| Dark/Light Theme | :heavy_check_mark: |
|
||||
| Search in deep | :heavy_check_mark: |
|
||||
| Inbound Multi User | :heavy_check_mark: |
|
||||
| Features | Enable? |
|
||||
| ------------------------------------ | :----------------: |
|
||||
| Multi-lang | :heavy_check_mark: |
|
||||
| Dark/Light Theme | :heavy_check_mark: |
|
||||
| Search in deep | :heavy_check_mark: |
|
||||
| Inbound Multi User | :heavy_check_mark: |
|
||||
| Multi User Traffic & Expiration time | :heavy_check_mark: |
|
||||
| REST API | :heavy_check_mark: |
|
||||
| Telegram BOT (admin + clients) | :heavy_check_mark: |
|
||||
| Backup database using Telegram BOT | :heavy_check_mark: |
|
||||
| Subscription link | :heavy_check_mark: |
|
||||
| REST API | :heavy_check_mark: |
|
||||
| Telegram BOT (admin + clients) | :heavy_check_mark: |
|
||||
| Backup database using Telegram BOT | :heavy_check_mark: |
|
||||
| Subscription link | :heavy_check_mark: |
|
||||
| Calculate expire date on first usage | :heavy_check_mark: |
|
||||
|
||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||
|
||||
# Features
|
||||
|
||||
@@ -44,19 +46,28 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
||||
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
||||
- `/xui/API/inbounds` base for following actions:
|
||||
|
||||
| Method | Path | Action |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| GET | "/" | Get all inbounds |
|
||||
| GET | "/get/:id" | Get inbound with inbound.id |
|
||||
| POST | "/add" | Add inbound |
|
||||
| POST | "/del/:id" | Delete Inbound |
|
||||
| POST | "/update/:id" | Update Inbound |
|
||||
| POST | "/addClient/" | Add Client to inbound |
|
||||
| POST | "/delClient/:email" | Delete Client |
|
||||
| POST | "/updateClient/:index" | Update Client |
|
||||
| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic |
|
||||
| POST | "/resetAllTraffics" | Reset traffics of all inbounds |
|
||||
| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound |
|
||||
| Method | Path | Action |
|
||||
| ------ | -------------------------------- | ------------------------------------------- |
|
||||
| GET | "/" | Get all inbounds |
|
||||
| GET | "/get/:id" | Get inbound with inbound.id |
|
||||
| POST | "/add" | Add inbound |
|
||||
| POST | "/del/:id" | Delete Inbound |
|
||||
| POST | "/update/:id" | Update Inbound |
|
||||
| POST | "/addClient/" | Add Client to inbound |
|
||||
| POST | "/delClient/:email" | Delete Client |
|
||||
| POST | "/updateClient/:index" | Update Client |
|
||||
| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic |
|
||||
| POST | "/resetAllTraffics" | Reset traffics of all inbounds |
|
||||
| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound |
|
||||
|
||||
# Environment Variables
|
||||
|
||||
| Variable | Type | Default |
|
||||
| -------------- | :--------------------------------------------: | :------------ |
|
||||
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||
| XUI_DEBUG | `boolean` | `false` |
|
||||
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||
|
||||
# Screenshot from Inbouds page
|
||||
|
||||
@@ -77,9 +88,9 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.s
|
||||
```
|
||||
|
||||
## Install custom version
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `0.4.0`:
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.4.0
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
|
||||
```
|
||||
|
||||
## Manual install & upgrade
|
||||
@@ -130,13 +141,10 @@ docker build -t x-ui .
|
||||
```
|
||||
|
||||
## SSL certificate application
|
||||
|
||||
<details>
|
||||
<summary>Click for details</summary>
|
||||
|
||||
### Cloudflare
|
||||
|
||||
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
||||
|
||||
### Certbot
|
||||
|
||||
```bash
|
||||
@@ -146,14 +154,14 @@ ln -s /snap/bin/certbot /usr/bin/certbot
|
||||
|
||||
certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d <Your Domain Name>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Tg robot use
|
||||
|
||||
<details>
|
||||
<summary>Click for details</summary>
|
||||
|
||||
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
||||
|
||||
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
||||
Set the robot-related parameters in the panel background, including:
|
||||
|
||||
@@ -167,8 +175,8 @@ Set the robot-related parameters in the panel background, including:
|
||||
|
||||
Reference syntax:
|
||||
|
||||
- 30 * * * * * //Notify at the 30s of each point
|
||||
- 0 */10 * * * * //Notify at the first second of each 10 minutes
|
||||
- 30 \* \* \* \* \* //Notify at the 30s of each point
|
||||
- 0 */10 \* \* \* \* //Notify at the first second of each 10 minutes
|
||||
- @hourly // hourly notification
|
||||
- @daily // Daily notification (00:00 in the morning)
|
||||
- @every 8h // notify every 8 hours
|
||||
@@ -190,6 +198,7 @@ Reference syntax:
|
||||
</details>
|
||||
|
||||
# Common problem
|
||||
|
||||
<details>
|
||||
<summary>Click for details</summary>
|
||||
## Migrating from v2-ui
|
||||
@@ -206,12 +215,15 @@ x-ui v2-ui
|
||||
|
||||
**If you upgrade from an old version or other forks, for enable traffic for users you should do :**
|
||||
|
||||
find this in config :
|
||||
``` json
|
||||
find this in config :
|
||||
|
||||
```json
|
||||
"policy": {
|
||||
"system": {
|
||||
```
|
||||
**and add this just after ` "policy": {` :**
|
||||
|
||||
**and add this just after ` "policy": {` :**
|
||||
|
||||
```json
|
||||
"levels": {
|
||||
"0": {
|
||||
@@ -222,6 +234,7 @@ find this in config :
|
||||
```
|
||||
|
||||
**the final output is like :**
|
||||
|
||||
```json
|
||||
"policy": {
|
||||
"levels": {
|
||||
@@ -238,13 +251,15 @@ find this in config :
|
||||
},
|
||||
"routing": {
|
||||
```
|
||||
restart panel
|
||||
|
||||
restart panel
|
||||
|
||||
</details>
|
||||
|
||||
# a special thanks to
|
||||
|
||||
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
||||
- [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
||||
- [MHSanaei](https://github.com/MHSanaei)
|
||||
- [MHSanaei](https://github.com/MHSanaei)
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
|
||||
@@ -45,6 +45,22 @@ func IsDebug() bool {
|
||||
return os.Getenv("XUI_DEBUG") == "true"
|
||||
}
|
||||
|
||||
func GetDBPath() string {
|
||||
return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName())
|
||||
func GetBinFolderPath() string {
|
||||
binFolderPath := os.Getenv("XUI_BIN_FOLDER")
|
||||
if binFolderPath == "" {
|
||||
binFolderPath = "bin"
|
||||
}
|
||||
return binFolderPath
|
||||
}
|
||||
|
||||
func GetDBFolderPath() string {
|
||||
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||
if dbFolderPath == "" {
|
||||
dbFolderPath = "/etc/x-ui"
|
||||
}
|
||||
return dbFolderPath
|
||||
}
|
||||
|
||||
func GetDBPath() string {
|
||||
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.5.0
|
||||
1.0.0_beta1
|
||||
@@ -74,7 +74,7 @@ type Client struct {
|
||||
Email string `json:"email"`
|
||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
Enable bool `json:"enable" from:"enable"`
|
||||
TgID string `json:"tgId" from:"tgId"`
|
||||
SubID string `json:"subId" from:"subId"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
TgID string `json:"tgId" form:"tgId"`
|
||||
SubID string `json:"subId" form:"subId"`
|
||||
}
|
||||
|
||||
11
go.mod
11
go.mod
@@ -7,17 +7,18 @@ require (
|
||||
github.com/gin-contrib/sessions v0.0.4
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.0.7
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.2
|
||||
github.com/shirou/gopsutil/v3 v3.23.3
|
||||
github.com/xtls/xray-core v1.8.0
|
||||
go.uber.org/atomic v1.10.0
|
||||
golang.org/x/text v0.8.0
|
||||
golang.org/x/text v0.9.0
|
||||
google.golang.org/grpc v1.54.0
|
||||
gorm.io/driver/sqlite v1.4.4
|
||||
gorm.io/gorm v1.24.6
|
||||
gorm.io/driver/sqlite v1.5.0
|
||||
gorm.io/gorm v1.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -29,7 +30,6 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
@@ -46,6 +46,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.4 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
|
||||
28
go.sum
28
go.sum
@@ -44,8 +44,8 @@ github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVL
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-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/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
@@ -70,7 +70,6 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -132,8 +131,12 @@ github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08=
|
||||
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
|
||||
github.com/sagernet/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/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU=
|
||||
github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M=
|
||||
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
||||
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
||||
github.com/shoenig/go-m1cpu v0.1.4 h1:SZPIgRM2sEF9NJy50mRHu9PKGwxyyTTJIWvCtgVbozs=
|
||||
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -205,7 +208,6 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -215,8 +217,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -244,11 +246,11 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
||||
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
|
||||
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
@@ -70,7 +70,7 @@ fi
|
||||
|
||||
|
||||
install_base() {
|
||||
if [[ "${release}" == "centos" ]]; then
|
||||
if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]] ; then
|
||||
yum install wget curl tar -y
|
||||
else
|
||||
apt install wget curl tar -y
|
||||
|
||||
2
main.go
2
main.go
@@ -217,7 +217,7 @@ func main() {
|
||||
|
||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
||||
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)
|
||||
var port int
|
||||
|
||||
@@ -246,6 +246,11 @@
|
||||
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 {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #161b22;
|
||||
|
||||
@@ -43,13 +43,9 @@ const RULE_DOMAIN = {
|
||||
SPEEDTEST: 'geosite:speedtest',
|
||||
};
|
||||
|
||||
const XTLS_FLOW_CONTROL = {
|
||||
ORIGIN: "xtls-rprx-origin",
|
||||
DIRECT: "xtls-rprx-direct",
|
||||
};
|
||||
|
||||
const TLS_FLOW_CONTROL = {
|
||||
VISION: "xtls-rprx-vision",
|
||||
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||
};
|
||||
|
||||
const TLS_VERSION_OPTION = {
|
||||
@@ -103,7 +99,6 @@ Object.freeze(VmessMethods);
|
||||
Object.freeze(SSMethods);
|
||||
Object.freeze(RULE_IP);
|
||||
Object.freeze(RULE_DOMAIN);
|
||||
Object.freeze(XTLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_VERSION_OPTION);
|
||||
Object.freeze(TLS_CIPHER_OPTION);
|
||||
@@ -478,8 +473,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
maxVersion = TLS_VERSION_OPTION.TLS12,
|
||||
cipherSuites = '',
|
||||
certificates=[new TlsStreamSettings.Cert()],
|
||||
alpn=[''],
|
||||
settings=[new TlsStreamSettings.Settings()]) {
|
||||
alpn=[],
|
||||
settings=new TlsStreamSettings.Settings()) {
|
||||
super();
|
||||
this.server = serverName;
|
||||
this.minVersion = minVersion;
|
||||
@@ -506,8 +501,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||
let values = json.settings[0];
|
||||
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
|
||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
||||
}
|
||||
return new TlsStreamSettings(
|
||||
json.serverName,
|
||||
@@ -528,7 +522,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
cipherSuites: this.cipherSuites,
|
||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||
alpn: this.alpn,
|
||||
settings: TlsStreamSettings.toJsonArray(this.settings),
|
||||
settings: this.settings,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -597,10 +591,92 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||
}
|
||||
};
|
||||
|
||||
class RealityStreamSettings extends XrayCommonClass {
|
||||
constructor(show = false, xver = 0,
|
||||
dest = 'microsoft.com:443',
|
||||
serverNames = 'microsoft.com,www.microsoft.com',
|
||||
privateKey = '', minClient = '', maxClient = '',
|
||||
maxTimediff = 0, shortIds = [],
|
||||
settings= new RealityStreamSettings.Settings()) {
|
||||
super();
|
||||
this.show = show;
|
||||
this.xver = xver;
|
||||
this.dest = dest;
|
||||
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
|
||||
this.privateKey = privateKey;
|
||||
this.minClient = minClient;
|
||||
this.maxClient = maxClient;
|
||||
this.maxTimediff = maxTimediff;
|
||||
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
let settings;
|
||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName, json.settings.spiderX);
|
||||
}
|
||||
return new RealityStreamSettings(
|
||||
json.show,
|
||||
json.xver,
|
||||
json.dest,
|
||||
json.serverNames,
|
||||
json.privateKey,
|
||||
json.minClient,
|
||||
json.maxClient,
|
||||
json.maxTimediff,
|
||||
json.shortIds,
|
||||
json.settings,
|
||||
);
|
||||
|
||||
}
|
||||
toJson() {
|
||||
return {
|
||||
show: this.show,
|
||||
xver: this.xver,
|
||||
dest: this.dest,
|
||||
serverNames: this.serverNames.split(","),
|
||||
privateKey: this.privateKey,
|
||||
minClient: this.minClient,
|
||||
maxClient: this.maxClient,
|
||||
maxTimediff: this.maxTimediff,
|
||||
shortIds: this.shortIds.split(","),
|
||||
settings: this.settings,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||
constructor(publicKey = '', fingerprint = '', serverName = '', spiderX= '/') {
|
||||
super();
|
||||
this.publicKey = publicKey;
|
||||
this.fingerprint = fingerprint;
|
||||
this.serverName = serverName;
|
||||
this.spiderX = spiderX;
|
||||
}
|
||||
static fromJson(json = {}) {
|
||||
return new RealityStreamSettings.Settings(
|
||||
json.publicKey,
|
||||
json.fingerprint,
|
||||
json.serverName,
|
||||
json.spiderX,
|
||||
);
|
||||
}
|
||||
toJson() {
|
||||
return {
|
||||
publicKey: this.publicKey,
|
||||
fingerprint: this.fingerprint,
|
||||
serverName: this.serverName,
|
||||
spiderX: this.spiderX,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class StreamSettings extends XrayCommonClass {
|
||||
constructor(network='tcp',
|
||||
security='none',
|
||||
tlsSettings=new TlsStreamSettings(),
|
||||
realitySettings = new RealityStreamSettings(),
|
||||
tcpSettings=new TcpStreamSettings(),
|
||||
kcpSettings=new KcpStreamSettings(),
|
||||
wsSettings=new WsStreamSettings(),
|
||||
@@ -612,6 +688,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
this.network = network;
|
||||
this.security = security;
|
||||
this.tls = tlsSettings;
|
||||
this.reality = realitySettings;
|
||||
this.tcp = tcpSettings;
|
||||
this.kcp = kcpSettings;
|
||||
this.ws = wsSettings;
|
||||
@@ -632,29 +709,25 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get isXTls() {
|
||||
return this.security === "xtls";
|
||||
get isReality() {
|
||||
return this.security === "reality";
|
||||
}
|
||||
|
||||
set isXTls(isXTls) {
|
||||
if (isXTls) {
|
||||
this.security = 'xtls';
|
||||
set isReality(isReality) {
|
||||
if (isReality) {
|
||||
this.security = 'reality';
|
||||
} else {
|
||||
this.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
let tls;
|
||||
if (json.security === "xtls") {
|
||||
tls = TlsStreamSettings.fromJson(json.xtlsSettings);
|
||||
} else {
|
||||
tls = TlsStreamSettings.fromJson(json.tlsSettings);
|
||||
}
|
||||
|
||||
return new StreamSettings(
|
||||
json.network,
|
||||
json.security,
|
||||
tls,
|
||||
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||
RealityStreamSettings.fromJson(json.realitySettings),
|
||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||
WsStreamSettings.fromJson(json.wsSettings),
|
||||
@@ -670,7 +743,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
network: network,
|
||||
security: this.security,
|
||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||
xtlsSettings: this.isXTls ? this.tls.toJson() : undefined,
|
||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||
@@ -750,13 +823,13 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get xtls() {
|
||||
return this.stream.security === 'xtls';
|
||||
get reality() {
|
||||
return this.stream.security === 'reality';
|
||||
}
|
||||
|
||||
set xtls(isXTls) {
|
||||
if (isXTls) {
|
||||
this.stream.security = 'xtls';
|
||||
set reality(isReality) {
|
||||
if (isReality) {
|
||||
this.stream.security = 'reality';
|
||||
} else {
|
||||
this.stream.security = 'none';
|
||||
}
|
||||
@@ -865,7 +938,7 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
|
||||
get serverName() {
|
||||
if (this.stream.isTls || this.stream.isXTls) {
|
||||
if (this.stream.isTls || this.stream.isReality) {
|
||||
return this.stream.tls.server;
|
||||
}
|
||||
return "";
|
||||
@@ -961,7 +1034,7 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
//this is used for xtls-rprx-vision
|
||||
canEnableTlsFlow() {
|
||||
if ((this.stream.security === 'tls') && (this.network === "tcp")) {
|
||||
if ((this.stream.security != 'none') && (this.network === "tcp")) {
|
||||
switch (this.protocol) {
|
||||
case Protocols.VLESS:
|
||||
return true;
|
||||
@@ -976,7 +1049,7 @@ class Inbound extends XrayCommonClass {
|
||||
return this.canEnableTls();
|
||||
}
|
||||
|
||||
canEnableXTls() {
|
||||
canEnableReality() {
|
||||
switch (this.protocol) {
|
||||
case Protocols.VLESS:
|
||||
case Protocols.TROJAN:
|
||||
@@ -984,7 +1057,15 @@ class Inbound extends XrayCommonClass {
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return this.network === "tcp";
|
||||
|
||||
switch (this.network) {
|
||||
case "tcp":
|
||||
case "http":
|
||||
case "grpc":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
canEnableStream() {
|
||||
@@ -1081,10 +1162,10 @@ class Inbound extends XrayCommonClass {
|
||||
host: host,
|
||||
path: path,
|
||||
tls: this.stream.security,
|
||||
sni: this.stream.tls.settings[0]['serverName'],
|
||||
fp: this.stream.tls.settings[0]['fingerprint'],
|
||||
sni: this.stream.tls.settings.serverName,
|
||||
fp: this.stream.tls.settings.fingerprint,
|
||||
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));
|
||||
}
|
||||
@@ -1143,32 +1224,43 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
if (this.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);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
if(this.stream.tls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
||||
if (this.stream.tls.settings.serverName !== ''){
|
||||
params.set("sni", this.stream.tls.settings.serverName);
|
||||
}
|
||||
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.xtls) {
|
||||
params.set("security", "xtls");
|
||||
params.set("alpn", this.stream.tls.alpn);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
if (this.reality) {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
if (this.stream.reality.shortIds != "") {
|
||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||
}
|
||||
if (this.stream.reality.fingerprint != "") {
|
||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||
address = this.stream.reality.settings.serverName;
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||
params.set("spx", this.stream.reality.settings.spiderX);
|
||||
}
|
||||
if (this.stream.network === 'tcp') {
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
|
||||
const link = `vless://${uuid}@${address}:${port}`;
|
||||
@@ -1247,29 +1339,40 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
if (this.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);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
if(this.stream.tls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
||||
if (this.stream.tls.settings.serverName !== ''){
|
||||
params.set("sni", this.stream.tls.settings.serverName);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.xtls) {
|
||||
params.set("security", "xtls");
|
||||
params.set("alpn", this.stream.tls.alpn);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
if (this.reality) {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||
}
|
||||
if (this.stream.reality.shortIds != "") {
|
||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||
}
|
||||
if (this.stream.reality.fingerprint != "") {
|
||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||
address = this.stream.reality.settings.serverName;
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||
params.set("spx", this.stream.reality.settings.spiderX);
|
||||
}
|
||||
if (this.stream.network === 'tcp') {
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||
}
|
||||
|
||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
||||
@@ -1515,7 +1618,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||
toJson() {
|
||||
return {
|
||||
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
|
||||
decryption: this.decryption,
|
||||
decryption: 'none',
|
||||
fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/add", a.addInbound)
|
||||
g.POST("/del/:id", a.delInbound)
|
||||
g.POST("/update/:id", a.updateInbound)
|
||||
g.POST("/addClient/", a.addInboundClient)
|
||||
g.POST("/addClient", a.addInboundClient)
|
||||
g.POST("/delClient/:email", a.delInboundClient)
|
||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
@@ -129,19 +129,19 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
inbound := &model.Inbound{}
|
||||
err := c.ShouldBind(inbound)
|
||||
data := &model.Inbound{}
|
||||
err := c.ShouldBind(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.inboundService.AddInboundClient(inbound)
|
||||
err = a.inboundService.AddInboundClient(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client added", nil)
|
||||
jsonMsg(c, "Client(s) added", nil)
|
||||
if err == nil {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/restartXrayService", a.restartXrayService)
|
||||
g.POST("/installXray/:version", a.installXray)
|
||||
g.POST("/logs/:count", a.getLogs)
|
||||
g.POST("/getConfigJson", a.getConfigJson)
|
||||
g.GET("/getDb", a.getDb)
|
||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||
}
|
||||
|
||||
func (a *ServerController) refreshStatus() {
|
||||
@@ -112,8 +115,40 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
||||
count := c.Param("count")
|
||||
logs, err := a.serverService.GetLogs(count)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
||||
jsonMsg(c, "getLogs", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, logs, nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||
configJson, err := a.serverService.GetConfigJson()
|
||||
if err != nil {
|
||||
jsonMsg(c, "get config.json", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, configJson, nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getDb(c *gin.Context) {
|
||||
db, err := a.serverService.GetDb()
|
||||
if err != nil {
|
||||
jsonMsg(c, "get Database", err)
|
||||
return
|
||||
}
|
||||
// Set the headers for the response
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", "attachment; filename=x-ui.db")
|
||||
|
||||
// Write the file contents to the response
|
||||
c.Writer.Write(db)
|
||||
}
|
||||
|
||||
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
|
||||
cert, err := a.serverService.GetNewX25519Cert()
|
||||
if err != nil {
|
||||
jsonMsg(c, "get x25519 certificate", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, cert, nil)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||
g = g.Group("/setting")
|
||||
|
||||
g.POST("/all", a.getAllSetting)
|
||||
g.POST("/thresholds", a.getThresholds)
|
||||
g.POST("/defaultSettings", a.getDefaultSettings)
|
||||
g.POST("/update", a.updateSetting)
|
||||
g.POST("/updateUser", a.updateUser)
|
||||
g.POST("/restartPanel", a.restartPanel)
|
||||
@@ -48,7 +48,7 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
jsonObj(c, allSetting, nil)
|
||||
}
|
||||
|
||||
func (a *SettingController) getThresholds(c *gin.Context) {
|
||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||
expireDiff, err := a.settingService.GetExpireDiff()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||
@@ -59,9 +59,21 @@ func (a *SettingController) getThresholds(c *gin.Context) {
|
||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||
return
|
||||
}
|
||||
defaultCert, err := a.settingService.GetCertFile()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||
return
|
||||
}
|
||||
defaultKey, err := a.settingService.GetKeyFile()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||
return
|
||||
}
|
||||
result := map[string]interface{}{
|
||||
"expireDiff": expireDiff,
|
||||
"trafficDiff": trafficDiff,
|
||||
"defaultCert": defaultCert,
|
||||
"defaultKey": defaultKey,
|
||||
}
|
||||
jsonObj(c, result, nil)
|
||||
}
|
||||
|
||||
@@ -4,73 +4,140 @@
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option :value="0">Random</a-select-option>
|
||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
||||
<a-select-option :value="4">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item><br />
|
||||
<a-form-item v-if="clientsBulkModal.emailMethod>1">
|
||||
<span slot="label">{{ i18n "pages.client.first" }}</span>
|
||||
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="clientsBulkModal.emailMethod>1">
|
||||
<span slot="label">{{ i18n "pages.client.last" }}</span>
|
||||
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="clientsBulkModal.emailMethod>0">
|
||||
<span slot="label">{{ i18n "pages.client.prefix" }}</span>
|
||||
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 120px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="clientsBulkModal.emailMethod>2">
|
||||
<span slot="label">{{ i18n "pages.client.postfix" }}</span>
|
||||
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 120px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="clientsBulkModal.emailMethod < 2">
|
||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="Subscription">
|
||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Telegram ID">
|
||||
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="{{ i18n "pages.client.delayedStart" }}">
|
||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientsBulkModal.delayedStart">
|
||||
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.method" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option :value="0">Random</a-select-option>
|
||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
||||
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod>1">
|
||||
<td>{{ i18n "pages.client.first" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod>1">
|
||||
<td>{{ i18n "pages.client.last" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod>0">
|
||||
<td>{{ i18n "pages.client.prefix" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod>2">
|
||||
<td>{{ i18n "pages.client.postfix" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod < 2">
|
||||
<td>{{ i18n "pages.client.clientCount" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
||||
<td>Flow</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subscription</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Telegram Username</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.delayedStart">
|
||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
@@ -83,7 +150,6 @@
|
||||
confirm: null,
|
||||
dbInbound: new DBInbound(),
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
quantity: 1,
|
||||
totalGB: 0,
|
||||
expiryTime: '',
|
||||
@@ -94,8 +160,10 @@
|
||||
emailPostfix: "",
|
||||
subId: "",
|
||||
tgId: "",
|
||||
flow: "",
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
clients = [];
|
||||
method=clientsBulkModal.emailMethod;
|
||||
if(method>1){
|
||||
start=clientsBulkModal.firstNum;
|
||||
@@ -115,9 +183,12 @@
|
||||
newClient.tgId = clientsBulkModal.tgId;
|
||||
newClient._totalGB = clientsBulkModal.totalGB;
|
||||
newClient._expiryTime = clientsBulkModal.expiryTime;
|
||||
clientsBulkModal.clients.push(newClient);
|
||||
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
||||
newClient.flow = clientsBulkModal.flow;
|
||||
}
|
||||
clients.push(newClient);
|
||||
}
|
||||
ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound);
|
||||
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
||||
this.visible = true;
|
||||
@@ -134,9 +205,9 @@
|
||||
this.emailPostfix= "";
|
||||
this.subId= "";
|
||||
this.tgId= "";
|
||||
this.flow= "";
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
||||
this.delayedStart = false;
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
confirmLoading: false,
|
||||
title: '',
|
||||
okText: '',
|
||||
isEdit: false,
|
||||
dbInbound: new DBInbound(),
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
@@ -20,9 +21,13 @@
|
||||
isExpired: false,
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index);
|
||||
if(clientModal.isEdit){
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.index);
|
||||
} else {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||
}
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=(index, dbInbound)=>{}, isEdit=false }) {
|
||||
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
|
||||
this.visible = true;
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||
</template>
|
||||
<template v-else-if="type === 'number'">
|
||||
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||
<a-input-number :value="value" @input="$emit('input', $event.target.value)"></a-input-number>
|
||||
</template>
|
||||
<template v-else-if="type === 'textarea'">
|
||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
||||
|
||||
@@ -3,88 +3,143 @@
|
||||
<template v-if="isEdit">
|
||||
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||
</template>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.email"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="{{ i18n "pages.inbounds.enable" }}">
|
||||
<a-switch v-model="client.enable"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="password" v-if="inbound.protocol === Protocols.TROJAN">
|
||||
<a-input v-model.trim="client.password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="id" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||
<a-input v-model.trim="client.id"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
||||
<a-input type="number" v-model.number="client.alterId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Subscription" v-if="client.email">
|
||||
<a-input v-model.trim="client.subId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Telegram Username" v-if="client.email">
|
||||
<a-input v-model.trim="client.tgId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.xtls" label="flow">
|
||||
<a-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 v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline">
|
||||
<a-select v-model="client.flow" style="width: 150px">
|
||||
<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>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
<template v-if="isEdit && clientStats">
|
||||
<span>{{ i18n "usage" }}:</span>
|
||||
<a-tag :color="statsColor">
|
||||
[[ sizeFormat(clientStats.up) ]] /
|
||||
[[ sizeFormat(clientStats.down) ]]
|
||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label="{{ i18n "pages.client.delayedStart" }}">
|
||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientModal.delayedStart">
|
||||
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.enable" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="client.enable"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.email" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.protocol === Protocols.TROJAN">
|
||||
<td>password</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.password" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||
<td>ID</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.id" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.protocol === Protocols.VMESS">
|
||||
<td>{{ i18n "additional" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="client.alterId"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email">
|
||||
<td>Subscription</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email">
|
||||
<td>Telegram Username</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.canEnableTlsFlow()">
|
||||
<td>Flow</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="isEdit && clientStats">
|
||||
<td>{{ i18n "usage" }}</td>
|
||||
<td>
|
||||
<a-tag :color="statsColor">
|
||||
[[ sizeFormat(clientStats.up) ]] /
|
||||
[[ sizeFormat(clientStats.down) ]]
|
||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||
</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientModal.delayedStart">
|
||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,58 +1,91 @@
|
||||
{{define "form/inbound"}}
|
||||
<!-- base -->
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "remark" }}'>
|
||||
<a-input v-model.trim="dbInbound.remark"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "enable" }}'>
|
||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "protocol" }}'>
|
||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
{{ i18n "monitor" }}
|
||||
<a-tooltip>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "enable" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "remark" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "protocol" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "monitor" }}
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="inbound.listen"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||
<a-input type="number" v-model.number="inbound.port"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.port"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess settings -->
|
||||
|
||||
@@ -1,20 +1,42 @@
|
||||
{{define "form/dokodemo"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||
<a-input type="number" v-model.number="inbound.settings.port"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="FollowRedirect">
|
||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.targetAddress"}}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.destinationPort"}}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.network"}}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>FollowRedirect</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,10 +1,22 @@
|
||||
{{define "form/http"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "username"}}'>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "username"}}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,19 +1,36 @@
|
||||
{{define "form/shadowsocks"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "encryption" }}'>
|
||||
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "encryption" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,24 +1,50 @@
|
||||
{{define "form/socks"}}
|
||||
<a-form layout="inline">
|
||||
<!-- <a-form-item label="Password authentication">-->
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="inbound.settings.auth === 'password'">
|
||||
<a-form-item label='{{ i18n "username" }}'>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||
</a-form-item>
|
||||
<tr>
|
||||
<td>{{ i18n "username" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'>
|
||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.settings.udp"
|
||||
label="IP">
|
||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||
</a-form-item>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.enable" }} udp</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP</td>
|
||||
<td>
|
||||
<a-form-item v-if="inbound.settings.udp">
|
||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -2,9 +2,9 @@
|
||||
<a-form layout="inline">
|
||||
<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-form layout="inline">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
@@ -12,45 +12,88 @@
|
||||
</template>
|
||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.email"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-form-item label="password">
|
||||
<a-input v-model.trim="client.password"></a-input>
|
||||
</a-form-item>
|
||||
<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-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>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>password</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subscription</td>
|
||||
<td>
|
||||
<a-form-item v-if="client.email">
|
||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Telegram Username</td>
|
||||
<td>
|
||||
<a-form-item v-if="client.email">
|
||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="delayedStart">
|
||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-collapse v-else>
|
||||
@@ -65,7 +108,7 @@
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)">
|
||||
<template v-if="inbound.isTcp && inbound.tls">
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Fallbacks">
|
||||
<a-row>
|
||||
@@ -97,7 +140,7 @@
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="xver">
|
||||
<a-input type="number" v-model.number="fallback.xver"></a-input>
|
||||
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
||||
</a-form>
|
||||
|
||||
@@ -2,61 +2,109 @@
|
||||
<a-form layout="inline">
|
||||
<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-form layout="inline">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.email"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-form-item label="id">
|
||||
<a-input v-model.trim="client.id"></a-input>
|
||||
</a-form-item>
|
||||
<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-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>
|
||||
</a-form-item>
|
||||
<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-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>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>id</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.canEnableTlsFlow()">
|
||||
<td>flow</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subscription</td>
|
||||
<td>
|
||||
<a-form-item v-if="client.email">
|
||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Telegram Username</td>
|
||||
<td>
|
||||
<a-form-item v-if="client.email">
|
||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="delayedStart">
|
||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-collapse v-else>
|
||||
@@ -71,7 +119,7 @@
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)">
|
||||
<template v-if="inbound.isTcp && inbound.tls">
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Fallbacks">
|
||||
<a-row>
|
||||
@@ -103,7 +151,7 @@
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="xver">
|
||||
<a-input type="number" v-model.number="fallback.xver"></a-input>
|
||||
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
||||
</a-form>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<a-form layout="inline">
|
||||
<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-form layout="inline">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
@@ -12,42 +12,96 @@
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.email"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-form-item label="id">
|
||||
<a-input v-model.trim="client.id"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
||||
<a-input type="number" v-model.number="client.alterId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 300px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>id</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "additional" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="client.alterId"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subscription</td>
|
||||
<td>
|
||||
<a-form-item v-if="client.email">
|
||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Telegram Username</td>
|
||||
<td>
|
||||
<a-form-item v-if="client.email">
|
||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="delayedStart">
|
||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-collapse v-else>
|
||||
@@ -64,8 +118,7 @@
|
||||
</a-collapse>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
||||
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
||||
<a-switch v-model="inbound.settings.disableInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
{{end}}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{{define "form/streamGRPC"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="serviceName">
|
||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>serviceName</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,12 +1,24 @@
|
||||
{{define "form/streamHTTP"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="host">
|
||||
<a-row v-for="(host, index) in inbound.stream.http.host">
|
||||
<a-input v-model.trim="inbound.stream.http.host[index]"></a-input>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "path" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>host</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-row v-for="(host, index) in inbound.stream.http.host">
|
||||
<a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,38 +1,85 @@
|
||||
{{define "form/streamKCP"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video(camouflage WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls(camouflage DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard(camouflage wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.number="inbound.stream.kcp.seed"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="mtu">
|
||||
<a-input type="number" v-model.number="inbound.stream.kcp.mtu"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="tti (ms)">
|
||||
<a-input type="number" v-model.number="inbound.stream.kcp.tti"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="uplink capacity (MB/S)">
|
||||
<a-input type="number" v-model.number="inbound.stream.kcp.upCap"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="downlink capacity (MB/S)">
|
||||
<a-input type="number" v-model.number="inbound.stream.kcp.downCap"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="congestion">
|
||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="read buffer size (MB)">
|
||||
<a-input type="number" v-model.number="inbound.stream.kcp.readBuffer"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="write buffer size (MB)">
|
||||
<a-input type="number" v-model.number="inbound.stream.kcp.writeBuffer"></a-input>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "camouflage" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mtu</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tti (ms)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uplink capacity (MB/S)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>downlink capacity (MB/S)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>congestion</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>read buffer size (MB)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>write buffer size (MB)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,24 +1,41 @@
|
||||
{{define "form/streamQUIC"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="none">none</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video(camouflage WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls(camouflage DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard(camouflage wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.quic.security" style="width: 200px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="none">none</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.quic.key" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "camouflage" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.quic.type" style="width: 200px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,16 +1,24 @@
|
||||
{{define "form/streamSettings"}}
|
||||
<!-- select stream network -->
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "transmission" }}'>
|
||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="kcp">kcp</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="quic">quic</a-select-option>
|
||||
<a-select-option value="grpc">grpc</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "transmission" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="kcp">kcp</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="quic">quic</a-select-option>
|
||||
<a-select-option value="grpc">grpc</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
|
||||
<!-- tcp -->
|
||||
|
||||
@@ -13,74 +13,109 @@
|
||||
</a-form>
|
||||
|
||||
<!-- tcp request -->
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'"
|
||||
layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestPath" }}'>
|
||||
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.request.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- tcp response -->
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'"
|
||||
layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.response.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.requestPath" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;"></a-input>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.request.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- tcp response -->
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.response.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,33 +1,47 @@
|
||||
{{define "form/streamWS"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="acceptProxyProtocol">
|
||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.ws.addHeader('Host', '')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.ws.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>acceptProxyProtocol</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "path" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.ws.path" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.ws.addHeader('Host', '')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.ws.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,74 +1,241 @@
|
||||
{{define "form/tlsSettings"}}
|
||||
<!-- tls enable -->
|
||||
<a-form layout="inline" v-if="inbound.canSetTls()">
|
||||
<a-form-item label="tls">
|
||||
<a-form v-if="inbound.canSetTls()" layout="inline">
|
||||
<a-form-item label="TLS">
|
||||
<a-switch v-model="inbound.tls">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.canEnableXTls()" label="xtls">
|
||||
<a-switch v-model="inbound.xtls"></a-switch>
|
||||
<a-form-item v-if="inbound.canEnableReality()" label="Reality">
|
||||
<a-switch v-model="inbound.reality"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- tls settings -->
|
||||
<a-form v-if="inbound.tls || inbound.xtls" layout="inline">
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls">
|
||||
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="CipherSuites">
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
|
||||
<a-select-option value="">auto</a-select-option>
|
||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="MinVersion">
|
||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="MaxVersion">
|
||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS" v-if="inbound.tls" >
|
||||
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "domainName" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tls.server"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Alpn">
|
||||
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
|
||||
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="Allow insecure">
|
||||
<a-switch v-model="inbound.stream.tls.settings[0].allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "certificate" }}'>
|
||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<template v-if="inbound.stream.tls.certs[0].useFile">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tls.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.tls.certs[0].keyFile" style="width:300px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form v-if="inbound.tls" layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>SNI</td>
|
||||
<td>
|
||||
<a-form-item placeholder="Server Name Indication" v-if="inbound.tls">
|
||||
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CipherSuites</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value="">auto</a-select-option>
|
||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MinVersion</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MaxVersion</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "domainName" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Alpn</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-checkbox-group v-model="inbound.stream.tls.alpn">
|
||||
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allow insecure</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a-form-item label="{{ i18n "certificate" }}">
|
||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="inbound.stream.tls.certs[0].useFile">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
|
||||
</a-form-item>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</a-form>
|
||||
|
||||
<!-- reality settings -->
|
||||
<a-form v-if="inbound.reality" layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "domainName" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Show</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.reality.show"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Xver</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item >
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 250px">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dest</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Server Names</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Short Ids</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SpiderX</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Private Key</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Public Key</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -49,9 +49,9 @@
|
||||
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>
|
||||
</td>
|
||||
<td v-else-if="inbound.xtls">
|
||||
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>
|
||||
<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>
|
||||
@@ -240,7 +240,7 @@
|
||||
return infoModal.dbInbound.isEnable;
|
||||
},
|
||||
get subBase() {
|
||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + "/sub/";
|
||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + basePath + "sub/";
|
||||
},
|
||||
get tgBase() {
|
||||
return "https://t.me/"
|
||||
|
||||
@@ -43,6 +43,14 @@
|
||||
loading(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 = {
|
||||
@@ -62,6 +70,7 @@
|
||||
inModal: inModal,
|
||||
Protocols: protocols,
|
||||
SSMethods: SSMethods,
|
||||
delayedStart: false,
|
||||
get inbound() {
|
||||
return inModal.inbound;
|
||||
},
|
||||
@@ -70,31 +79,39 @@
|
||||
},
|
||||
get 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: {
|
||||
streamNetworkChange(oldValue) {
|
||||
if (oldValue === 'kcp') {
|
||||
this.inModal.inbound.tls = false;
|
||||
streamNetworkChange() {
|
||||
if (!inModal.inbound.canSetTls()) {
|
||||
this.inModal.inbound.stream.security = 'none';
|
||||
}
|
||||
if (!inModal.inbound.canEnableReality()) {
|
||||
this.inModal.inbound.reality = false;
|
||||
}
|
||||
},
|
||||
addClient(protocol, clients) {
|
||||
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;
|
||||
setDefaultCertData(){
|
||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||
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;
|
||||
}
|
||||
},
|
||||
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
|
||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||
},
|
||||
getNewEmail(client) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">tls</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXTls" color="cyan">xtls</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">reality</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="clients" slot-scope="text, dbInbound">
|
||||
@@ -209,11 +209,10 @@
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script>
|
||||
|
||||
const columns = [{
|
||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||
align: 'center',
|
||||
width: 30,
|
||||
width: 40,
|
||||
scopedSlots: { customRender: 'action' },
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.enable" }}',
|
||||
@@ -221,7 +220,7 @@
|
||||
width: 40,
|
||||
scopedSlots: { customRender: 'enable' },
|
||||
}, {
|
||||
title: "Id",
|
||||
title: "ID",
|
||||
align: 'center',
|
||||
dataIndex: "id",
|
||||
width: 30,
|
||||
@@ -287,6 +286,8 @@
|
||||
searchedInbounds: [],
|
||||
expireDiff: 0,
|
||||
trafficDiff: 0,
|
||||
defaultCert: '',
|
||||
defaultKey: '',
|
||||
clientCount: {},
|
||||
},
|
||||
methods: {
|
||||
@@ -303,15 +304,17 @@
|
||||
this.setInbounds(msg.obj);
|
||||
this.searchKey = '';
|
||||
},
|
||||
async getThresholds() {
|
||||
async getDefaultSettings() {
|
||||
this.loading();
|
||||
const msg = await HttpUtil.post('/xui/setting/thresholds');
|
||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
this.expireDiff = msg.obj.expireDiff * 86400000;
|
||||
this.trafficDiff = msg.obj.trafficDiff * 1073741824;
|
||||
this.defaultCert = msg.obj.defaultCert;
|
||||
this.defaultKey = msg.obj.defaultKey;
|
||||
},
|
||||
setInbounds(dbInbounds) {
|
||||
this.inbounds.splice(0);
|
||||
@@ -344,7 +347,7 @@
|
||||
depleted.push(client.email);
|
||||
} else {
|
||||
if ((client.expiryTime > 0 && (client.expiryTime-now < this.expireDiff)) ||
|
||||
(client.total > 0 && (client.total-client.up+client.down < this.trafficDiff ))) expiring.push(client.email);
|
||||
(client.total > 0 && (client.total-(client.up+client.down) < this.trafficDiff ))) expiring.push(client.email);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -525,9 +528,9 @@
|
||||
title: '{{ i18n "pages.client.add"}}',
|
||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (inbound, dbInbound, index) => {
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
clientModal.loading();
|
||||
await this.addClient(inbound, dbInbound);
|
||||
await this.addClient(clients, dbInboundId);
|
||||
clientModal.close();
|
||||
},
|
||||
isEdit: false
|
||||
@@ -539,9 +542,9 @@
|
||||
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
||||
okText: '{{ i18n "pages.client.bulk"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (inbound, dbInbound) => {
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
clientsBulkModal.loading();
|
||||
await this.addClient(inbound, dbInbound);
|
||||
await this.addClient(clients, dbInboundId);
|
||||
clientsBulkModal.close();
|
||||
},
|
||||
});
|
||||
@@ -555,9 +558,9 @@
|
||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||
dbInbound: dbInbound,
|
||||
index: index,
|
||||
confirm: async (inbound, dbInbound, index) => {
|
||||
confirm: async (client, dbInboundId, index) => {
|
||||
clientModal.loading();
|
||||
await this.updateClient(inbound, dbInbound, index);
|
||||
await this.updateClient(client, dbInboundId, index);
|
||||
clientModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
@@ -567,17 +570,17 @@
|
||||
firstKey = Object.keys(client)[0];
|
||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||
},
|
||||
async addClient(inbound, dbInbound) {
|
||||
async addClient(clients, dbInboundId) {
|
||||
const data = {
|
||||
id: dbInbound.id,
|
||||
settings: inbound.settings.toString(),
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + clients.toString() +']}',
|
||||
};
|
||||
await this.submit('/xui/inbound/addClient', data);
|
||||
await this.submit(`/xui/inbound/addClient`, data);
|
||||
},
|
||||
async updateClient(inbound, dbInbound, index) {
|
||||
async updateClient(client, dbInboundId, index) {
|
||||
const data = {
|
||||
id: dbInbound.id,
|
||||
settings: inbound.settings.toString(),
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() +']}',
|
||||
};
|
||||
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
||||
},
|
||||
@@ -744,7 +747,7 @@
|
||||
}, 500)
|
||||
},
|
||||
mounted() {
|
||||
this.getThresholds();
|
||||
this.getDefaultSettings();
|
||||
this.getDBInbounds();
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -76,24 +76,12 @@
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
{{ i18n "pages.index.xrayStatus" }}:
|
||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||
<a-tooltip v-if="status.xray.state === State.Error">
|
||||
<template slot="title">
|
||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||
x-ui: <a-tag color="green">{{ .cur_ver }}</a-tag>
|
||||
xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
x-ui: <a-tag color="green">{{ .cur_ver }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Logs</a-tag>
|
||||
{{ i18n "pages.index.operationHours" }}:
|
||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||
<a-tooltip>
|
||||
@@ -104,6 +92,29 @@
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
{{ i18n "pages.index.xrayStatus" }}:
|
||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||
<a-tooltip v-if="status.xray.state === State.Error">
|
||||
<template slot="title">
|
||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
{{ i18n "menu.link" }}:
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Logs</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||
@@ -221,6 +232,7 @@
|
||||
</a-modal>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
{{template "textModal"}}
|
||||
<script>
|
||||
|
||||
const State = {
|
||||
@@ -401,6 +413,18 @@
|
||||
return;
|
||||
}
|
||||
logModal.show(msg.obj,rows);
|
||||
},
|
||||
async openConfig(){
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('server/getConfigJson');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
txtModal.show('config.json',JSON.stringify(msg.obj, null, 2),'config.json');
|
||||
},
|
||||
getBackup(){
|
||||
window.location = basePath + 'server/getDb';
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
|
||||
@@ -64,28 +64,45 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) {
|
||||
func (s *InboundService) getAllEmails() ([]string, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan})
|
||||
if ignoreId > 0 {
|
||||
db = db.Where("id != ?", ignoreId)
|
||||
}
|
||||
db = db.Find(&inbounds)
|
||||
if db.Error != nil {
|
||||
return "", db.Error
|
||||
}
|
||||
var emails []string
|
||||
err := db.Raw(`
|
||||
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
`).Scan(&emails).Error
|
||||
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.getClients(inbound)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if err != nil {
|
||||
return nil, 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 {
|
||||
if emails[client.Email] {
|
||||
func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
|
||||
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
|
||||
}
|
||||
if s.contains(allEmails, client.Email) {
|
||||
return client.Email, nil
|
||||
}
|
||||
emails = append(emails, client.Email)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
@@ -96,16 +113,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
emails := make(map[string]bool)
|
||||
allEmails, err := s.getAllEmails()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var emails []string
|
||||
for _, client := range clients {
|
||||
if client.Email != "" {
|
||||
if emails[client.Email] {
|
||||
if s.contains(emails, client.Email) {
|
||||
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) {
|
||||
@@ -201,14 +225,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
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)
|
||||
if err != nil {
|
||||
return inbound, err
|
||||
@@ -231,8 +247,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
return inbound, db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
||||
clients, err := s.getClients(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -241,29 +261,35 @@ func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
||||
return common.NewError("Duplicate email:", existEmail)
|
||||
}
|
||||
|
||||
clients, err := s.getClients(inbound)
|
||||
oldInbound, err := s.GetInbound(data.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound, err := s.GetInbound(inbound.Id)
|
||||
var settings map[string]interface{}
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldClients, err := s.getClients(oldInbound)
|
||||
oldClients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
for _, client := range clients {
|
||||
newClients = append(newClients, client)
|
||||
}
|
||||
|
||||
settings["clients"] = append(oldClients, newClients...)
|
||||
|
||||
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound.Settings = inbound.Settings
|
||||
oldInbound.Settings = string(newSettings)
|
||||
|
||||
if len(clients[len(clients)-1].Email) > 0 {
|
||||
s.AddClientStat(inbound.Id, &clients[len(clients)-1])
|
||||
}
|
||||
for i := len(oldClients); i < len(clients); i++ {
|
||||
if len(clients[i].Email) > 0 {
|
||||
s.AddClientStat(inbound.Id, &clients[i])
|
||||
for _, client := range clients {
|
||||
if len(client.Email) > 0 {
|
||||
s.AddClientStat(data.Id, &client)
|
||||
}
|
||||
}
|
||||
db := database.GetDB()
|
||||
@@ -289,21 +315,13 @@ func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string)
|
||||
return db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error {
|
||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existEmail != "" {
|
||||
return common.NewError("Duplicate email:", existEmail)
|
||||
}
|
||||
|
||||
clients, err := s.getClients(inbound)
|
||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
|
||||
clients, err := s.getClients(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound, err := s.GetInbound(inbound.Id)
|
||||
oldInbound, err := s.GetInbound(data.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -313,18 +331,45 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound.Settings = inbound.Settings
|
||||
if len(clients[0].Email) > 0 && clients[0].Email != oldClients[index].Email {
|
||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existEmail != "" {
|
||||
return common.NewError("Duplicate email:", existEmail)
|
||||
}
|
||||
}
|
||||
|
||||
var settings map[string]interface{}
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settingsClients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
newClients = append(newClients, clients[0])
|
||||
settingsClients[index] = newClients[0]
|
||||
|
||||
settings["clients"] = settingsClients
|
||||
|
||||
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound.Settings = string(newSettings)
|
||||
db := database.GetDB()
|
||||
|
||||
if len(clients[index].Email) > 0 {
|
||||
if len(clients[0].Email) > 0 {
|
||||
if len(oldClients[index].Email) > 0 {
|
||||
err = s.UpdateClientStat(oldClients[index].Email, &clients[index])
|
||||
err = s.UpdateClientStat(oldClients[index].Email, &clients[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
s.AddClientStat(inbound.Id, &clients[index])
|
||||
s.AddClientStat(data.Id, &clients[0])
|
||||
}
|
||||
} else {
|
||||
err = s.DelClientStat(db, oldClients[index].Email)
|
||||
@@ -405,7 +450,7 @@ func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_tr
|
||||
}
|
||||
}()
|
||||
|
||||
for traffic_index, traffic := range traffics {
|
||||
for _, traffic := range traffics {
|
||||
inbound := &model.Inbound{}
|
||||
client_traffic := &xray.ClientTraffic{}
|
||||
err := db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(client_traffic).Error
|
||||
@@ -425,25 +470,34 @@ func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_tr
|
||||
}
|
||||
continue
|
||||
}
|
||||
// get settings clients
|
||||
settings := map[string][]model.Client{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients := settings["clients"]
|
||||
// get clients
|
||||
clients, err := s.getClients(inbound)
|
||||
needUpdate := false
|
||||
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
|
||||
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
|
||||
}
|
||||
client_traffic.ExpiryTime = client.ExpiryTime
|
||||
client_traffic.Total = client.TotalGB
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if needUpdate {
|
||||
settings["clients"] = clients
|
||||
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
|
||||
@@ -455,9 +509,9 @@ func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_tr
|
||||
}
|
||||
}
|
||||
|
||||
traffics[traffic_index] = client_traffic
|
||||
full_traffics = append(full_traffics, client_traffic)
|
||||
}
|
||||
return traffics, nil
|
||||
return full_traffics, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||
@@ -480,6 +534,17 @@ func (s *InboundService) DisableInvalidClients() (int64, error) {
|
||||
count := result.RowsAffected
|
||||
return count, err
|
||||
}
|
||||
func (s *InboundService) RemoveOrphanedTraffics() {
|
||||
db := database.GetDB()
|
||||
db.Exec(`
|
||||
DELETE FROM client_traffics
|
||||
WHERE email NOT IN (
|
||||
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
)
|
||||
`)
|
||||
}
|
||||
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
||||
db := database.GetDB()
|
||||
|
||||
@@ -552,6 +617,7 @@ func (s *InboundService) ResetAllTraffics() error {
|
||||
db := database.GetDB()
|
||||
|
||||
result := db.Model(model.Inbound{}).
|
||||
Where("user_id > ?", 0).
|
||||
Updates(map[string]interface{}{"up": 0, "down": 0})
|
||||
|
||||
err := result.Error
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/sys"
|
||||
"x-ui/xray"
|
||||
@@ -193,9 +194,11 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versions := make([]string, 0, len(releases))
|
||||
var versions []string
|
||||
for _, release := range releases {
|
||||
versions = append(versions, release.TagName)
|
||||
if release.TagName >= "v1.8.0" {
|
||||
versions = append(versions, release.TagName)
|
||||
}
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
@@ -349,3 +352,69 @@ func (s *ServerService) GetLogs(count string) ([]string, error) {
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||
// Open the file for reading
|
||||
file, err := os.Open(xray.GetConfigPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read the file contents
|
||||
fileContents, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var jsonData interface{}
|
||||
err = json.Unmarshal(fileContents, &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetDb() ([]byte, error) {
|
||||
// Open the file for reading
|
||||
file, err := os.Open(config.GetDBPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read the file contents
|
||||
fileContents, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fileContents, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(out.String(), "\n")
|
||||
|
||||
privateKeyLine := strings.Split(lines[0], ":")
|
||||
publicKeyLine := strings.Split(lines[1], ":")
|
||||
|
||||
privateKey := strings.TrimSpace(privateKeyLine[1])
|
||||
publicKey := strings.TrimSpace(publicKeyLine[1])
|
||||
|
||||
keyPair := map[string]interface{}{
|
||||
"privateKey": privateKey,
|
||||
"publicKey": publicKey,
|
||||
}
|
||||
|
||||
return keyPair, nil
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
|
||||
case "client_traffic":
|
||||
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
||||
case "client_commands":
|
||||
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
|
||||
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":
|
||||
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>")
|
||||
}
|
||||
@@ -363,6 +363,11 @@ func (t *Tgbot) getInboundUsages() string {
|
||||
}
|
||||
|
||||
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
||||
if len(tgUserName) == 0 {
|
||||
msg := "Your configuration is not found!\nYou should configure your telegram username and ask Admin to add it to your configuration."
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
@@ -373,6 +378,7 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
||||
if len(traffics) == 0 {
|
||||
msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram username in your configuration(s).\n\nYour username: <b>@" + tgUserName + "</b>"
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
for _, traffic := range traffics {
|
||||
expiryTime := ""
|
||||
@@ -530,14 +536,14 @@ func (t *Tgbot) getExhausted() string {
|
||||
for _, inbound := range inbounds {
|
||||
if inbound.Enable {
|
||||
if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) ||
|
||||
(inbound.Total > 0 && (inbound.Total-inbound.Up+inbound.Down < trDiff)) {
|
||||
(inbound.Total > 0 && (inbound.Total-(inbound.Up+inbound.Down) < trDiff)) {
|
||||
exhaustedInbounds = append(exhaustedInbounds, *inbound)
|
||||
}
|
||||
if len(inbound.ClientStats) > 0 {
|
||||
for _, client := range inbound.ClientStats {
|
||||
if client.Enable {
|
||||
if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) ||
|
||||
(client.Total > 0 && (client.Total-client.Up+client.Down < trDiff)) {
|
||||
(client.Total > 0 && (client.Total-(client.Up+client.Down) < trDiff)) {
|
||||
exhaustedClients = append(exhaustedClients, client)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -69,6 +69,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
}
|
||||
|
||||
s.inboundService.DisableInvalidClients()
|
||||
s.inboundService.RemoveOrphanedTraffics()
|
||||
|
||||
inbounds, err := s.inboundService.GetAllInbounds()
|
||||
if err != nil {
|
||||
@@ -104,16 +105,13 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
}
|
||||
|
||||
// clear client config for additional parameters
|
||||
indexDecrease := 0
|
||||
for index, client := range clients {
|
||||
var final_clients []interface{}
|
||||
for _, client := range clients {
|
||||
|
||||
c := client.(map[string]interface{})
|
||||
|
||||
// remove disabled clients
|
||||
if c["enable"] != nil {
|
||||
if enable, ok := c["enable"].(bool); ok && !enable {
|
||||
clients = RemoveIndex(clients, index-indexDecrease)
|
||||
indexDecrease++
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -122,10 +120,10 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
delete(c, key)
|
||||
}
|
||||
}
|
||||
clients[index-indexDecrease] = interface{}(c)
|
||||
final_clients = append(final_clients, interface{}(c))
|
||||
}
|
||||
|
||||
settings["clients"] = clients
|
||||
settings["clients"] = final_clients
|
||||
modifiedSettings, err := json.Marshal(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"depleted" = "Depleted"
|
||||
"depletingSoon" = "Depleting soon"
|
||||
"domainName" = "Domain name"
|
||||
"additional" = "Alter"
|
||||
"additional" = "Alter ID"
|
||||
"monitor" = "Listen IP"
|
||||
"certificate" = "Certificat"
|
||||
"fail" = "Fail"
|
||||
@@ -106,8 +106,8 @@
|
||||
"expireDate" = "Expire date"
|
||||
"resetTraffic" = "Reset traffic"
|
||||
"addInbound" = "Add Inbound"
|
||||
"addTo" = "Add To"
|
||||
"revise" = "Revise"
|
||||
"addTo" = "Create"
|
||||
"revise" = "Update"
|
||||
"modifyInbound" = "Modify InBound"
|
||||
"deleteInbound" = "Delete Inbound"
|
||||
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
||||
@@ -143,6 +143,7 @@
|
||||
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
||||
"Email" = "Email"
|
||||
"EmailDesc" = "The Email Must Be Completely Unique"
|
||||
"setDefaultCert" = "Set cert from panel"
|
||||
|
||||
[pages.client]
|
||||
"add" = "Add client"
|
||||
@@ -155,7 +156,7 @@
|
||||
"first" = "First"
|
||||
"last" = "Last"
|
||||
"prefix" = "Prefix"
|
||||
"postfix" = "postfix"
|
||||
"postfix" = "Postfix"
|
||||
"delayedStart" = "Start after first use"
|
||||
"expireDays" = "Expire days"
|
||||
"days" = "day(s)"
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
"network" = "شبکه"
|
||||
"destinationPort" = "پورت مقصد"
|
||||
"targetAddress" = "آدرس مقصد"
|
||||
"disableInsecureEncryption" = "رمزگذاری ناامن را غیرفعال کنید"
|
||||
"disableInsecureEncryption" = "غیرفعال سازی رمزگذاری ناامن"
|
||||
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
||||
"meansNoLimit" = "یعنی بدون محدودیت"
|
||||
"totalFlow" = "کل ترافیک"
|
||||
@@ -143,6 +143,7 @@
|
||||
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
|
||||
"Email" = "ایمیل"
|
||||
"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||
|
||||
[pages.client]
|
||||
"add" = "کاربر جدید"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"depleted" = "耗尽"
|
||||
"depletingSoon" = "即将耗尽"
|
||||
"domainName" = "域名"
|
||||
"additional" = "额外"
|
||||
"additional" = "额外 ID"
|
||||
"monitor" = "监听"
|
||||
"certificate" = "证书"
|
||||
"fail" = "失败"
|
||||
@@ -143,6 +143,7 @@
|
||||
"resetAllClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
|
||||
"Email" = "电子邮件"
|
||||
"EmailDesc" = "电子邮件必须完全唯"
|
||||
"setDefaultCert" = "从面板设置证书"
|
||||
|
||||
[pages.client]
|
||||
"add" = "添加客户端"
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"x-ui/config"
|
||||
"x-ui/util/common"
|
||||
|
||||
"github.com/Workiva/go-datastructures/queue"
|
||||
@@ -29,19 +30,19 @@ func GetBinaryName() string {
|
||||
}
|
||||
|
||||
func GetBinaryPath() string {
|
||||
return "bin/" + GetBinaryName()
|
||||
return config.GetBinFolderPath() + "/" + GetBinaryName()
|
||||
}
|
||||
|
||||
func GetConfigPath() string {
|
||||
return "bin/config.json"
|
||||
return config.GetBinFolderPath() + "/config.json"
|
||||
}
|
||||
|
||||
func GetGeositePath() string {
|
||||
return "bin/geosite.dat"
|
||||
return config.GetBinFolderPath() + "/geosite.dat"
|
||||
}
|
||||
|
||||
func GetGeoipPath() string {
|
||||
return "bin/geoip.dat"
|
||||
return config.GetBinFolderPath() + "/geoip.dat"
|
||||
}
|
||||
|
||||
func stopProcess(p *Process) {
|
||||
|
||||
Reference in New Issue
Block a user