Compare commits

...

25 Commits
0.5.2 ... 1.0.0

Author SHA1 Message Date
Alireza Ahmadi
73d518dfe3 v1.0.0 2023-04-19 02:35:29 +02:00
Alireza Ahmadi
4592a8d201 typos 2023-04-19 02:02:49 +02:00
Alireza Ahmadi
6ab99326ce [docker] add initial files #221 2023-04-19 01:58:36 +02:00
Alireza Ahmadi
6c5b098e1d [reality] optimize links 2023-04-19 00:53:07 +02:00
Alireza Ahmadi
fe670ae885 [sub] reality link 2023-04-19 00:43:19 +02:00
Alireza Ahmadi
f4f1d477b3 Optimize client settings 2023-04-19 00:42:54 +02:00
Alireza Ahmadi
7455a2baa3 update packages 2023-04-18 16:49:05 +02:00
Alireza Ahmadi
4c5576c38b [reality] fix trojan link issue + sub 2023-04-18 16:31:04 +02:00
Alireza Ahmadi
18ee201412 [sub] add subscription-userinfo #217 2023-04-18 12:18:33 +02:00
Alireza Ahmadi
772a1b341e v1.0.0_beta1 2023-04-18 09:34:16 +02:00
Alireza Ahmadi
5923c765a9 Merge pull request #227 from alireza0:Reality
Reality
2023-04-18 09:32:12 +02:00
Alireza Ahmadi
d7074cc3c9 Update dockerfile and actions 2023-04-18 09:30:53 +02:00
Alireza Ahmadi
f4f8c0d6df simplify API calls #218 2023-04-18 01:22:33 +02:00
Alireza Ahmadi
0c755be648 add flow in bulk creation #213 2023-04-16 19:24:39 +02:00
Alireza Ahmadi
c8dc4a96b4 [Reality] make it pretty 2023-04-16 19:21:35 +02:00
Alireza Ahmadi
8d9f6ccc11 [Reality] initiate 2023-04-15 09:54:58 +02:00
Alireza Ahmadi
3d7a8e372e Merge pull request #205 from hamid-gh98/main
Support set db and bin folder path from env
2023-04-14 14:51:28 +02:00
Hamidreza Ghavami
2fe4f1ad07 update README.md 2023-04-14 16:56:35 +04:30
Hamidreza Ghavami
04095b6ec4 update README.md 2023-04-14 16:54:41 +04:30
Hamidreza Ghavami
6d404e4b27 update get paths functions 2023-04-14 04:44:04 +04:30
Hamidreza Ghavami
f9f8431dd2 update v2-ui.db path 2023-04-14 04:40:46 +04:30
Hamidreza Ghavami
f996b9ee2b update en lang 2023-04-14 04:39:35 +04:30
Hamidreza Ghavami
a3e1ee1f35 update db config path 2023-04-14 04:36:33 +04:30
Alireza Ahmadi
e16391ad05 Merge pull request #203 from lk29/main 2023-04-13 23:41:14 +02:00
lk29
48b543becb Update tgbot.go syntax fix ..Passowrd 2023-04-14 01:53:47 +05:00
49 changed files with 2011 additions and 980 deletions

View File

@@ -26,7 +26,7 @@ jobs:
mv xui-release x-ui mv xui-release x-ui
mkdir bin mkdir bin
cd bin cd bin
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-64.zip wget https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip geoip.dat geosite.dat rm -f Xray-linux-64.zip geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
@@ -66,7 +66,7 @@ jobs:
mv xui-release x-ui mv xui-release x-ui
mkdir bin mkdir bin
cd bin cd bin
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip wget https://github.com/xtls/xray-core/releases/download/v1.8.1/Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
@@ -106,7 +106,7 @@ jobs:
mv xui-release x-ui mv xui-release x-ui
mkdir bin mkdir bin
cd bin cd bin
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-s390x.zip wget https://github.com/xtls/xray-core/releases/download/v1.8.1/Xray-linux-s390x.zip
unzip Xray-linux-s390x.zip unzip Xray-linux-s390x.zip
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat rm -f Xray-linux-s390x.zip geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat

17
DockerInitFiles.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
if [ $1 == "amd64" ]; then
ARCH="64";
FNAME="amd64";
elif [ $1 == "arm64" ]; then
ARCH="arm64-v8a"
FNAME="arm64";
else
ARCH="64";
FNAME="amd64";
fi
mkdir -p build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-${ARCH}.zip"
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -o build/bin/geoip.dat
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -o build/bin/geosite.dat
mv xray "build/bin/xray-linux-${FNAME}"
mv main build/x-ui

View File

@@ -1,16 +1,18 @@
FROM golang:1.20-alpine AS builder FROM golang:1.20-alpine AS builder
WORKDIR /app WORKDIR /app
ENV CGO_ENABLED 1 ARG TARGETARCH
RUN apk add gcc && apk --no-cache --update add build-base RUN apk --no-cache --update add build-base gcc wget
COPY . . COPY . .
RUN go build main.go RUN env CGO_ENABLED=1 go build main.go
RUN ./DockerInitFiles.sh "$TARGETARCH"
FROM alpine FROM alpine
LABEL org.opencontainers.image.authors="alireza7@gmail.com" LABEL org.opencontainers.image.authors="alireza7@gmail.com"
ENV TZ=Asia/Tehran ENV TZ=Asia/Tehran
WORKDIR /app WORKDIR /app
RUN apk add ca-certificates tzdata && mkdir bin RUN apk add ca-certificates tzdata
COPY --from=builder /app/main /app/x-ui
COPY --from=builder /app/build/ /app/
VOLUME [ "/etc/x-ui" ] VOLUME [ "/etc/x-ui" ]
CMD [ "./x-ui" ] CMD [ "./x-ui" ]

View File

@@ -1,24 +1,26 @@
# x-ui # x-ui
![](https://img.shields.io/github/v/release/alireza0/x-ui.svg) ![](https://img.shields.io/github/v/release/alireza0/x-ui.svg)
![](https://img.shields.io/docker/pulls/alireza7/x-ui.svg) ![](https://img.shields.io/docker/pulls/alireza7/x-ui.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/alireza0/x-ui)](https://goreportcard.com/report/github.com/alireza0/x-ui) [![Go Report Card](https://goreportcard.com/badge/github.com/alireza0/x-ui)](https://goreportcard.com/report/github.com/alireza0/x-ui)
[![Downloads](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg) [![Downloads](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment** > **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)** xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
| Features | Enable? | | Features | Enable? |
| ------------- |:-------------:| | ------------------------------------ | :----------------: |
| Multi-lang | :heavy_check_mark: | | Multi-lang | :heavy_check_mark: |
| Dark/Light Theme | :heavy_check_mark: | | Dark/Light Theme | :heavy_check_mark: |
| Search in deep | :heavy_check_mark: | | Search in deep | :heavy_check_mark: |
| Inbound Multi User | :heavy_check_mark: | | Inbound Multi User | :heavy_check_mark: |
| Multi User Traffic & Expiration time | :heavy_check_mark: | | Multi User Traffic & Expiration time | :heavy_check_mark: |
| REST API | :heavy_check_mark: | | REST API | :heavy_check_mark: |
| Telegram BOT (admin + clients) | :heavy_check_mark: | | Telegram BOT (admin + clients) | :heavy_check_mark: |
| Backup database using Telegram BOT | :heavy_check_mark: | | Backup database using Telegram BOT | :heavy_check_mark: |
| Subscription link | :heavy_check_mark: | | Subscription link + userInfo | :heavy_check_mark: |
| Calculate expire date on first usage | :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:
@@ -44,19 +46,28 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login - `/login` with `PUSH` user data: `{username: '', password: ''}` for login
- `/xui/API/inbounds` base for following actions: - `/xui/API/inbounds` base for following actions:
| Method | Path | Action | | Method | Path | Action |
| ------------- | ------------- | ------------- | | :----: | --------------------------------- | ------------------------------------------- |
| GET | "/" | Get all inbounds | | `GET` | `"/"` | Get all inbounds |
| GET | "/get/:id" | Get inbound with inbound.id | | `GET` | `"/get/:id"` | Get inbound with inbound.id |
| POST | "/add" | Add inbound | | `POST` | `"/add"` | Add inbound |
| POST | "/del/:id" | Delete Inbound | | `POST` | `"/del/:id"` | Delete Inbound |
| POST | "/update/:id" | Update Inbound | | `POST` | `"/update/:id"` | Update Inbound |
| POST | "/addClient/" | Add Client to inbound | | `POST` | `"/addClient/"` | Add Client to inbound |
| POST | "/delClient/:email" | Delete Client | | `POST` | `"/delClient/:email"` | Delete Client |
| POST | "/updateClient/:index" | Update Client | | `POST` | `"/updateClient/:index"` | Update Client |
| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic | | `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
| POST | "/resetAllTraffics" | Reset traffics of all inbounds | | `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound | | `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
# Environment Variables
| Variable | Type | Default |
| -------------- | :--------------------------------------------: | :------------ |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
# Screenshot from Inbouds page # Screenshot from Inbouds page
@@ -67,7 +78,7 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
- CentOS 8+ - CentOS 8+
- Ubuntu 20+ - Ubuntu 20+
- Debian 8+ - Debian 10+
- Fedora 36+ - Fedora 36+
# Install & Upgrade to latest version # Install & Upgrade to latest version
@@ -77,9 +88,9 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.s
``` ```
## Install custom version ## Install custom version
To install your desired version you can add the version to the end of install command. Example for ver `0.5.1`: To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.1 bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
``` ```
## Manual install & upgrade ## Manual install & upgrade
@@ -130,13 +141,10 @@ docker build -t x-ui .
``` ```
## SSL certificate application ## SSL certificate application
<details> <details>
<summary>Click for details</summary> <summary>Click for details</summary>
### Cloudflare
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
### Certbot ### Certbot
```bash ```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> certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d <Your Domain Name>
``` ```
</details> </details>
## Tg robot use ## Tg robot use
<details> <details>
<summary>Click for details</summary> <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) X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
Set the robot-related parameters in the panel background, including: Set the robot-related parameters in the panel background, including:
@@ -167,8 +175,8 @@ Set the robot-related parameters in the panel background, including:
Reference syntax: Reference syntax:
- 30 * * * * * //Notify at the 30s of each point - 30 \* \* \* \* \* //Notify at the 30s of each point
- 0 */10 * * * * //Notify at the first second of each 10 minutes - 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
- @hourly // hourly notification - @hourly // hourly notification
- @daily // Daily notification (00:00 in the morning) - @daily // Daily notification (00:00 in the morning)
- @every 8h // notify every 8 hours - @every 8h // notify every 8 hours
@@ -190,6 +198,7 @@ Reference syntax:
</details> </details>
# Common problem # Common problem
<details> <details>
<summary>Click for details</summary> <summary>Click for details</summary>
## Migrating from v2-ui ## Migrating from v2-ui
@@ -207,11 +216,14 @@ x-ui v2-ui
**If you upgrade from an old version or other forks, for enable traffic for users you should do :** **If you upgrade from an old version or other forks, for enable traffic for users you should do :**
find this in config : find this in config :
``` json
```json
"policy": { "policy": {
"system": { "system": {
``` ```
**and add this just after ` "policy": {` :**
**and add this just after ` "policy": {` :**
```json ```json
"levels": { "levels": {
"0": { "0": {
@@ -222,6 +234,7 @@ find this in config :
``` ```
**the final output is like :** **the final output is like :**
```json ```json
"policy": { "policy": {
"levels": { "levels": {
@@ -238,10 +251,13 @@ find this in config :
}, },
"routing": { "routing": {
``` ```
restart panel
restart panel
</details> </details>
# a special thanks to # a special thanks to
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/) - [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
- [MHSanaei](https://github.com/MHSanaei) - [MHSanaei](https://github.com/MHSanaei)

View File

@@ -45,6 +45,22 @@ func IsDebug() bool {
return os.Getenv("XUI_DEBUG") == "true" return os.Getenv("XUI_DEBUG") == "true"
} }
func GetDBPath() string { func GetBinFolderPath() string {
return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName()) binFolderPath := os.Getenv("XUI_BIN_FOLDER")
if binFolderPath == "" {
binFolderPath = "bin"
}
return binFolderPath
}
func GetDBFolderPath() string {
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath == "" {
dbFolderPath = "/etc/x-ui"
}
return dbFolderPath
}
func GetDBPath() string {
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
} }

View File

@@ -1 +1 @@
0.5.2 1.0.0

View File

@@ -7,4 +7,6 @@ services:
- $PWD/db/:/etc/x-ui/ - $PWD/db/:/etc/x-ui/
- $PWD/cert/:/root/cert/ - $PWD/cert/:/root/cert/
restart: unless-stopped restart: unless-stopped
network_mode: host ports:
- 54321:54321
- 443:443

32
go.mod
View File

@@ -13,7 +13,7 @@ require (
github.com/pelletier/go-toml/v2 v2.0.7 github.com/pelletier/go-toml/v2 v2.0.7
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.23.3 github.com/shirou/gopsutil/v3 v3.23.3
github.com/xtls/xray-core v1.8.0 github.com/xtls/xray-core v1.8.1
go.uber.org/atomic v1.10.0 go.uber.org/atomic v1.10.0
golang.org/x/text v0.9.0 golang.org/x/text v0.9.0
google.golang.org/grpc v1.54.0 google.golang.org/grpc v1.54.0
@@ -23,13 +23,13 @@ require (
require ( require (
github.com/BurntSushi/toml v1.2.1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect
github.com/bytedance/sonic v1.8.0 // indirect github.com/bytedance/sonic v1.8.7 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect github.com/go-playground/validator/v10 v10.12.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
@@ -38,25 +38,25 @@ require (
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.3 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pires/go-proxyproto v0.6.2 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/shoenig/go-m1cpu v0.1.4 // indirect github.com/shoenig/go-m1cpu v0.1.5 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect github.com/tklauser/numcpus v0.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.7.0 // indirect golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.8.0 // indirect golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.6.0 // indirect golang.org/x/sys v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/protobuf v1.29.0 // indirect google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

79
go.sum
View File

@@ -11,6 +11,8 @@ github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
@@ -19,9 +21,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo= github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo= github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
@@ -41,7 +46,9 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@@ -58,7 +65,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso= github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
@@ -76,7 +83,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
@@ -85,17 +92,25 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo= github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -104,37 +119,41 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA= github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0= github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
github.com/refraction-networking/utls v1.2.3-0.20230308205431-4f1df6c200db h1:ULRv/GPW5KYDafE0FACN2no+HTCyQLUtfyOIeyp3GNc= github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08= github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q= github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE= github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU= 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 h1:SZPIgRM2sEF9NJy50mRHu9PKGwxyyTTJIWvCtgVbozs=
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ= github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c= github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -161,10 +180,12 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/xtls/reality v0.0.0-20230309125256-0d0713b108c8 h1:LLtLxEe3S0Ko+ckqt4t29RLskpNdOZfgjZCC2/Byr50= github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
github.com/xtls/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k= github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
github.com/xtls/xray-core v1.8.0/go.mod h1:i9KWgbLyxg/NT+3+g4nE74Zp3DgTCP3X04YkSfsJeDI= github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
@@ -174,23 +195,25 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -208,8 +231,9 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -224,19 +248,20 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -61,8 +61,8 @@ elif [[ "${release}" == "fedora" ]]; then
fi fi
elif [[ "${release}" == "debian" ]]; then elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 10 ]]; then
echo -e "${red} Please use Debian 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
fi fi
else else
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1 echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1

12
main.go
View File

@@ -97,7 +97,7 @@ func showSetting(show bool) {
settingService := service.SettingService{} settingService := service.SettingService{}
port, err := settingService.GetPort() port, err := settingService.GetPort()
if err != nil { if err != nil {
fmt.Println("get current port fialed,error info:", err) fmt.Println("get current port failed,error info:", err)
} }
userService := service.UserService{} userService := service.UserService{}
userModel, err := userService.GetFirstUser() userModel, err := userService.GetFirstUser()
@@ -109,7 +109,7 @@ func showSetting(show bool) {
if (username == "") || (userpasswd == "") { if (username == "") || (userpasswd == "") {
fmt.Println("current username or password is empty") fmt.Println("current username or password is empty")
} }
fmt.Println("current pannel settings as follows:") fmt.Println("current panel settings as follows:")
fmt.Println("username:", username) fmt.Println("username:", username)
fmt.Println("userpasswd:", userpasswd) fmt.Println("userpasswd:", userpasswd)
fmt.Println("port:", port) fmt.Println("port:", port)
@@ -217,7 +217,7 @@ func main() {
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError) v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
var dbPath string var dbPath string
v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path") v2uiCmd.StringVar(&dbPath, "db", fmt.Sprintf("%s/v2-ui.db", config.GetDBFolderPath()), "set v2-ui db file path")
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError) settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
var port int var port int
@@ -234,9 +234,9 @@ func main() {
settingCmd.IntVar(&port, "port", 0, "set panel port") settingCmd.IntVar(&port, "port", 0, "set panel port")
settingCmd.StringVar(&username, "username", "", "set login username") settingCmd.StringVar(&username, "username", "", "set login username")
settingCmd.StringVar(&password, "password", "", "set login password") settingCmd.StringVar(&password, "password", "", "set login password")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token") settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time") settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegrame bot chat id") settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify") settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
oldUsage := flag.Usage oldUsage := flag.Usage

View File

@@ -246,6 +246,11 @@
background-color: #2e3b52; background-color: #2e3b52;
} }
.ant-card-dark .ant-select-disabled .ant-select-selection {
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: #242c3a;
}
.ant-card-dark .ant-collapse-item { .ant-card-dark .ant-collapse-item {
color: hsla(0,0%,100%,.65); color: hsla(0,0%,100%,.65);
background-color: #161b22; background-color: #161b22;

View File

@@ -43,13 +43,9 @@ const RULE_DOMAIN = {
SPEEDTEST: 'geosite:speedtest', SPEEDTEST: 'geosite:speedtest',
}; };
const XTLS_FLOW_CONTROL = {
ORIGIN: "xtls-rprx-origin",
DIRECT: "xtls-rprx-direct",
};
const TLS_FLOW_CONTROL = { const TLS_FLOW_CONTROL = {
VISION: "xtls-rprx-vision", VISION: "xtls-rprx-vision",
VISION_UDP443: "xtls-rprx-vision-udp443",
}; };
const TLS_VERSION_OPTION = { const TLS_VERSION_OPTION = {
@@ -103,7 +99,6 @@ Object.freeze(VmessMethods);
Object.freeze(SSMethods); Object.freeze(SSMethods);
Object.freeze(RULE_IP); Object.freeze(RULE_IP);
Object.freeze(RULE_DOMAIN); Object.freeze(RULE_DOMAIN);
Object.freeze(XTLS_FLOW_CONTROL);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(TLS_VERSION_OPTION); Object.freeze(TLS_VERSION_OPTION);
Object.freeze(TLS_CIPHER_OPTION); Object.freeze(TLS_CIPHER_OPTION);
@@ -479,7 +474,7 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites = '', cipherSuites = '',
certificates=[new TlsStreamSettings.Cert()], certificates=[new TlsStreamSettings.Cert()],
alpn=[], alpn=[],
settings=[new TlsStreamSettings.Settings()]) { settings=new TlsStreamSettings.Settings()) {
super(); super();
this.server = serverName; this.server = serverName;
this.minVersion = minVersion; this.minVersion = minVersion;
@@ -506,8 +501,7 @@ class TlsStreamSettings extends XrayCommonClass {
} }
if (!ObjectUtil.isEmpty(json.settings)) { if (!ObjectUtil.isEmpty(json.settings)) {
let values = json.settings[0]; settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
} }
return new TlsStreamSettings( return new TlsStreamSettings(
json.serverName, json.serverName,
@@ -528,7 +522,7 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites: this.cipherSuites, cipherSuites: this.cipherSuites,
certificates: TlsStreamSettings.toJsonArray(this.certs), certificates: TlsStreamSettings.toJsonArray(this.certs),
alpn: this.alpn, alpn: this.alpn,
settings: TlsStreamSettings.toJsonArray(this.settings), settings: this.settings,
}; };
} }
} }
@@ -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 { class StreamSettings extends XrayCommonClass {
constructor(network='tcp', constructor(network='tcp',
security='none', security='none',
tlsSettings=new TlsStreamSettings(), tlsSettings=new TlsStreamSettings(),
realitySettings = new RealityStreamSettings(),
tcpSettings=new TcpStreamSettings(), tcpSettings=new TcpStreamSettings(),
kcpSettings=new KcpStreamSettings(), kcpSettings=new KcpStreamSettings(),
wsSettings=new WsStreamSettings(), wsSettings=new WsStreamSettings(),
@@ -612,6 +688,7 @@ class StreamSettings extends XrayCommonClass {
this.network = network; this.network = network;
this.security = security; this.security = security;
this.tls = tlsSettings; this.tls = tlsSettings;
this.reality = realitySettings;
this.tcp = tcpSettings; this.tcp = tcpSettings;
this.kcp = kcpSettings; this.kcp = kcpSettings;
this.ws = wsSettings; this.ws = wsSettings;
@@ -632,29 +709,25 @@ class StreamSettings extends XrayCommonClass {
} }
} }
get isXTls() { get isReality() {
return this.security === "xtls"; return this.security === "reality";
} }
set isXTls(isXTls) { set isReality(isReality) {
if (isXTls) { if (isReality) {
this.security = 'xtls'; this.security = 'reality';
} else { } else {
this.security = 'none'; this.security = 'none';
} }
} }
static fromJson(json={}) { static fromJson(json={}) {
let tls;
if (json.security === "xtls") {
tls = TlsStreamSettings.fromJson(json.xtlsSettings);
} else {
tls = TlsStreamSettings.fromJson(json.tlsSettings);
}
return new StreamSettings( return new StreamSettings(
json.network, json.network,
json.security, json.security,
tls, TlsStreamSettings.fromJson(json.tlsSettings),
RealityStreamSettings.fromJson(json.realitySettings),
TcpStreamSettings.fromJson(json.tcpSettings), TcpStreamSettings.fromJson(json.tcpSettings),
KcpStreamSettings.fromJson(json.kcpSettings), KcpStreamSettings.fromJson(json.kcpSettings),
WsStreamSettings.fromJson(json.wsSettings), WsStreamSettings.fromJson(json.wsSettings),
@@ -670,7 +743,7 @@ class StreamSettings extends XrayCommonClass {
network: network, network: network,
security: this.security, security: this.security,
tlsSettings: this.isTls ? this.tls.toJson() : undefined, tlsSettings: this.isTls ? this.tls.toJson() : undefined,
xtlsSettings: this.isXTls ? this.tls.toJson() : undefined, realitySettings: this.isReality ? this.reality.toJson() : undefined,
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined, tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined, kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
wsSettings: network === 'ws' ? this.ws.toJson() : undefined, wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
@@ -750,13 +823,13 @@ class Inbound extends XrayCommonClass {
} }
} }
get xtls() { get reality() {
return this.stream.security === 'xtls'; return this.stream.security === 'reality';
} }
set xtls(isXTls) { set reality(isReality) {
if (isXTls) { if (isReality) {
this.stream.security = 'xtls'; this.stream.security = 'reality';
} else { } else {
this.stream.security = 'none'; this.stream.security = 'none';
} }
@@ -865,7 +938,7 @@ class Inbound extends XrayCommonClass {
} }
get serverName() { get serverName() {
if (this.stream.isTls || this.stream.isXTls) { if (this.stream.isTls || this.stream.isReality) {
return this.stream.tls.server; return this.stream.tls.server;
} }
return ""; return "";
@@ -961,7 +1034,7 @@ class Inbound extends XrayCommonClass {
//this is used for xtls-rprx-vision //this is used for xtls-rprx-vision
canEnableTlsFlow() { canEnableTlsFlow() {
if ((this.stream.security === 'tls') && (this.network === "tcp")) { if ((this.stream.security != 'none') && (this.network === "tcp")) {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VLESS: case Protocols.VLESS:
return true; return true;
@@ -976,7 +1049,7 @@ class Inbound extends XrayCommonClass {
return this.canEnableTls(); return this.canEnableTls();
} }
canEnableXTls() { canEnableReality() {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VLESS: case Protocols.VLESS:
case Protocols.TROJAN: case Protocols.TROJAN:
@@ -984,7 +1057,15 @@ class Inbound extends XrayCommonClass {
default: default:
return false; return false;
} }
return this.network === "tcp";
switch (this.network) {
case "tcp":
case "http":
case "grpc":
return true;
default:
return false;
}
} }
canEnableStream() { canEnableStream() {
@@ -1081,10 +1162,10 @@ class Inbound extends XrayCommonClass {
host: host, host: host,
path: path, path: path,
tls: this.stream.security, tls: this.stream.security,
sni: this.stream.tls.settings[0]['serverName'], sni: this.stream.tls.settings.serverName,
fp: this.stream.tls.settings[0]['fingerprint'], fp: this.stream.tls.settings.fingerprint,
alpn: this.stream.tls.alpn.join(','), alpn: this.stream.tls.alpn.join(','),
allowInsecure: this.stream.tls.settings[0].allowInsecure, allowInsecure: this.stream.tls.settings.allowInsecure,
}; };
return 'vmess://' + base64(JSON.stringify(obj, null, 2)); return 'vmess://' + base64(JSON.stringify(obj, null, 2));
} }
@@ -1143,32 +1224,43 @@ class Inbound extends XrayCommonClass {
if (this.tls) { if (this.tls) {
params.set("security", "tls"); params.set("security", "tls");
params.set("fp" , this.stream.tls.settings[0]['fingerprint']); params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){ if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.tls.server;
} }
if (this.stream.tls.settings[0]['serverName'] !== ''){ if (this.stream.tls.settings.serverName !== ''){
params.set("sni", this.stream.tls.settings[0]['serverName']); params.set("sni", this.stream.tls.settings.serverName);
} }
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) { if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
params.set("flow", this.settings.vlesses[clientIndex].flow); params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
} }
if (this.xtls) { if (this.reality) {
params.set("security", "xtls"); params.set("security", "reality");
params.set("alpn", this.stream.tls.alpn); params.set("pbk", this.stream.reality.settings.publicKey);
if(this.stream.tls.settings[0].allowInsecure){ if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("allowInsecure", "1"); params.set("sni", this.stream.reality.serverNames.split(",")[0]);
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (this.stream.reality.shortIds.length > 0) {
address = this.stream.tls.server; params.set("sid", this.stream.reality.shortIds.split(",")[0]);
}
if (!ObjectUtil.isEmpty(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' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) {
params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
const link = `vless://${uuid}@${address}:${port}`; const link = `vless://${uuid}@${address}:${port}`;
@@ -1247,29 +1339,40 @@ class Inbound extends XrayCommonClass {
if (this.tls) { if (this.tls) {
params.set("security", "tls"); params.set("security", "tls");
params.set("fp" , this.stream.tls.settings[0]['fingerprint']); params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){ if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.tls.server;
} }
if (this.stream.tls.settings[0]['serverName'] !== ''){ if (this.stream.tls.settings.serverName !== ''){
params.set("sni", this.stream.tls.settings[0]['serverName']); params.set("sni", this.stream.tls.settings.serverName);
} }
} }
if (this.xtls) { if (this.reality) {
params.set("security", "xtls"); params.set("security", "reality");
params.set("alpn", this.stream.tls.alpn); params.set("pbk", this.stream.reality.settings.publicKey);
if(this.stream.tls.settings[0].allowInsecure){ if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("allowInsecure", "1"); params.set("sni", this.stream.reality.serverNames.split(",")[0]);
}
if (this.stream.reality.shortIds.length > 0) {
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
}
if (!ObjectUtil.isEmpty(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' && !ObjectUtil.isEmpty(this.settings.trojans[clientIndex].flow)) {
params.set("flow", this.settings.trojans[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)}`; const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;

View File

@@ -31,7 +31,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/add", a.addInbound) g.POST("/add", a.addInbound)
g.POST("/del/:id", a.delInbound) g.POST("/del/:id", a.delInbound)
g.POST("/update/:id", a.updateInbound) g.POST("/update/:id", a.updateInbound)
g.POST("/addClient/", a.addInboundClient) g.POST("/addClient", a.addInboundClient)
g.POST("/delClient/:email", a.delInboundClient) g.POST("/delClient/:email", a.delInboundClient)
g.POST("/updateClient/:index", a.updateInboundClient) g.POST("/updateClient/:index", a.updateInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
@@ -129,19 +129,19 @@ func (a *InboundController) updateInbound(c *gin.Context) {
} }
func (a *InboundController) addInboundClient(c *gin.Context) { func (a *InboundController) addInboundClient(c *gin.Context) {
inbound := &model.Inbound{} data := &model.Inbound{}
err := c.ShouldBind(inbound) err := c.ShouldBind(data)
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
return return
} }
err = a.inboundService.AddInboundClient(inbound) err = a.inboundService.AddInboundClient(data)
if err != nil { if err != nil {
jsonMsg(c, "something worng!", err) jsonMsg(c, "something worng!", err)
return return
} }
jsonMsg(c, "Client added", nil) jsonMsg(c, "Client(s) added", nil)
if err == nil { if err == nil {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }

View File

@@ -41,6 +41,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/logs/:count", a.getLogs) g.POST("/logs/:count", a.getLogs)
g.POST("/getConfigJson", a.getConfigJson) g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb) g.GET("/getDb", a.getDb)
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
} }
func (a *ServerController) refreshStatus() { func (a *ServerController) refreshStatus() {
@@ -114,7 +115,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
count := c.Param("count") count := c.Param("count")
logs, err := a.serverService.GetLogs(count) logs, err := a.serverService.GetLogs(count)
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err) jsonMsg(c, "getLogs", err)
return return
} }
jsonObj(c, logs, nil) jsonObj(c, logs, nil)
@@ -123,7 +124,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
func (a *ServerController) getConfigJson(c *gin.Context) { func (a *ServerController) getConfigJson(c *gin.Context) {
configJson, err := a.serverService.GetConfigJson() configJson, err := a.serverService.GetConfigJson()
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err) jsonMsg(c, "get config.json", err)
return return
} }
jsonObj(c, configJson, nil) jsonObj(c, configJson, nil)
@@ -132,7 +133,7 @@ func (a *ServerController) getConfigJson(c *gin.Context) {
func (a *ServerController) getDb(c *gin.Context) { func (a *ServerController) getDb(c *gin.Context) {
db, err := a.serverService.GetDb() db, err := a.serverService.GetDb()
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err) jsonMsg(c, "get Database", err)
return return
} }
// Set the headers for the response // Set the headers for the response
@@ -142,3 +143,12 @@ func (a *ServerController) getDb(c *gin.Context) {
// Write the file contents to the response // Write the file contents to the response
c.Writer.Write(db) c.Writer.Write(db)
} }
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
cert, err := a.serverService.GetNewX25519Cert()
if err != nil {
jsonMsg(c, "get x25519 certificate", err)
return
}
jsonObj(c, cert, nil)
}

View File

@@ -29,14 +29,18 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
func (a *SUBController) subs(c *gin.Context) { func (a *SUBController) subs(c *gin.Context) {
subId := c.Param("subid") subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0] host := strings.Split(c.Request.Host, ":")[0]
subs, err := a.subService.GetSubs(subId, host) subs, header, err := a.subService.GetSubs(subId, host)
if err != nil { if err != nil || len(subs) == 0 {
c.String(400, "Error!") c.String(400, "Error!")
} else { } else {
result := "" result := ""
for _, sub := range subs { for _, sub := range subs {
result += sub + "\n" result += sub + "\n"
} }
// Add subscription-userinfo
c.Writer.Header().Set("subscription-userinfo", header)
c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} }
} }

View File

@@ -4,73 +4,140 @@
:class="siderDrawer.isDarkTheme ? darkClass : ''" :class="siderDrawer.isDarkTheme ? darkClass : ''"
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'> :ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.client.method" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option :value="0">Random</a-select-option> <td>{{ i18n "pages.client.method" }}</td>
<a-select-option :value="1">Random+Prefix</a-select-option> <td>
<a-select-option :value="2">Random+Prefix+Num</a-select-option> <a-form-item>
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option> <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
<a-select-option :value="4">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option> :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
</a-select> <a-select-option :value="0">Random</a-select-option>
</a-form-item><br /> <a-select-option :value="1">Random+Prefix</a-select-option>
<a-form-item v-if="clientsBulkModal.emailMethod>1"> <a-select-option :value="2">Random+Prefix+Num</a-select-option>
<span slot="label">{{ i18n "pages.client.first" }}</span> <a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number> <a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
</a-form-item> </a-select>
<a-form-item v-if="clientsBulkModal.emailMethod>1"> </a-form-item>
<span slot="label">{{ i18n "pages.client.last" }}</span> </td>
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number> </tr>
</a-form-item> <tr v-if="clientsBulkModal.emailMethod>1">
<a-form-item v-if="clientsBulkModal.emailMethod>0"> <td>{{ i18n "pages.client.first" }}</td>
<span slot="label">{{ i18n "pages.client.prefix" }}</span> <td>
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 120px"></a-input> <a-form-item>
</a-form-item> <a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
<a-form-item v-if="clientsBulkModal.emailMethod>2"> </a-form-item>
<span slot="label">{{ i18n "pages.client.postfix" }}</span> </td>
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 120px"></a-input> </tr>
</a-form-item> <tr v-if="clientsBulkModal.emailMethod>1">
<a-form-item v-if="clientsBulkModal.emailMethod < 2"> <td>{{ i18n "pages.client.last" }}</td>
<span slot="label">{{ i18n "pages.client.clientCount" }}</span> <td>
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number> <a-form-item>
</a-form-item> <a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
<a-form-item label="Subscription"> </a-form-item>
<a-input v-model.trim="clientsBulkModal.subId"></a-input> </td>
</a-form-item> </tr>
<a-form-item label="Telegram ID"> <tr v-if="clientsBulkModal.emailMethod>0">
<a-input v-model.trim="clientsBulkModal.tgId"></a-input> <td>{{ i18n "pages.client.prefix" }}</td>
</a-form-item> <td>
<a-form-item> <a-form-item>
<span slot="label"> <a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) </a-form-item>
<a-tooltip> </td>
<template slot="title"> </tr>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <tr v-if="clientsBulkModal.emailMethod>2">
</template> <td>{{ i18n "pages.client.postfix" }}</td>
<a-icon type="question-circle" theme="filled"></a-icon> <td>
</a-tooltip> <a-form-item>
</span> <a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> </a-form-item>
</a-form-item> </td>
<a-form-item label="{{ i18n "pages.client.delayedStart" }}"> </tr>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch> <tr v-if="clientsBulkModal.emailMethod < 2">
</a-form-item> <td>{{ i18n "pages.client.clientCount" }}</td>
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientsBulkModal.delayedStart"> <td>
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input> <a-form-item>
</a-form-item> <a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
<a-form-item v-else> </a-form-item>
<span slot="label"> </td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span> </tr>
<a-tooltip> <tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
<template slot="title"> <td>Flow</td>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> <td>
</template> <a-form-item>
<a-icon type="question-circle" theme="filled"></a-icon> <a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
</a-tooltip> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
</span> <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" </a-select>
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" </a-form-item>
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker> </td>
</a-form-item> </tr>
<tr>
<td>Subscription</td>
<td>
<a-form-item>
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Telegram Username</td>
<td>
<a-form-item>
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
</a-modal> </a-modal>
<script> <script>
@@ -83,7 +150,6 @@
confirm: null, confirm: null,
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
inbound: new Inbound(), inbound: new Inbound(),
clients: [],
quantity: 1, quantity: 1,
totalGB: 0, totalGB: 0,
expiryTime: '', expiryTime: '',
@@ -94,8 +160,10 @@
emailPostfix: "", emailPostfix: "",
subId: "", subId: "",
tgId: "", tgId: "",
flow: "",
delayedStart: false, delayedStart: false,
ok() { ok() {
clients = [];
method=clientsBulkModal.emailMethod; method=clientsBulkModal.emailMethod;
if(method>1){ if(method>1){
start=clientsBulkModal.firstNum; start=clientsBulkModal.firstNum;
@@ -115,9 +183,12 @@
newClient.tgId = clientsBulkModal.tgId; newClient.tgId = clientsBulkModal.tgId;
newClient._totalGB = clientsBulkModal.totalGB; newClient._totalGB = clientsBulkModal.totalGB;
newClient._expiryTime = clientsBulkModal.expiryTime; newClient._expiryTime = clientsBulkModal.expiryTime;
clientsBulkModal.clients.push(newClient); if(clientsBulkModal.inbound.canEnableTlsFlow()){
newClient.flow = clientsBulkModal.flow;
}
clients.push(newClient);
} }
ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound); ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
}, },
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) { show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
this.visible = true; this.visible = true;
@@ -134,9 +205,9 @@
this.emailPostfix= ""; this.emailPostfix= "";
this.subId= ""; this.subId= "";
this.tgId= ""; this.tgId= "";
this.flow= "";
this.dbInbound = new DBInbound(dbInbound); this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
this.delayedStart = false; this.delayedStart = false;
}, },
getClients(protocol, clientSettings) { getClients(protocol, clientSettings) {

View File

@@ -12,6 +12,7 @@
confirmLoading: false, confirmLoading: false,
title: '', title: '',
okText: '', okText: '',
isEdit: false,
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
inbound: new Inbound(), inbound: new Inbound(),
clients: [], clients: [],
@@ -20,9 +21,13 @@
isExpired: false, isExpired: false,
delayedStart: false, delayedStart: false,
ok() { ok() {
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index); if(clientModal.isEdit){
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.index);
} else {
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
}
}, },
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=(index, dbInbound)=>{}, isEdit=false }) { show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
this.visible = true; this.visible = true;
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;

View File

@@ -9,7 +9,7 @@
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input> <a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
</template> </template>
<template v-else-if="type === 'number'"> <template v-else-if="type === 'number'">
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)"></a-input> <a-input-number :value="value" @input="$emit('input', $event.target.value)"></a-input-number>
</template> </template>
<template v-else-if="type === 'textarea'"> <template v-else-if="type === 'textarea'">
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea> <a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>

View File

@@ -3,88 +3,143 @@
<template v-if="isEdit"> <template v-if="isEdit">
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag> <a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
</template> </template>
<a-form-item> <table width="100%" class="ant-table-tbody">
<span slot="label"> <tr>
<span>{{ i18n "pages.inbounds.Email" }}</span> <td>{{ i18n "pages.inbounds.enable" }}</td>
<a-tooltip> <td>
<template slot="title"> <a-form-item>
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span> <a-switch v-model="client.enable"></a-switch>
</template> </a-form-item>
<a-icon type="sync" @click="getNewEmail(client)"></a-icon> </td>
</a-tooltip> </tr>
</span> <tr>
<a-input v-model.trim="client.email"></a-input> <td>
</a-form-item> <span>{{ i18n "pages.inbounds.Email" }}</span>
<a-form-item label="{{ i18n "pages.inbounds.enable" }}"> <a-tooltip>
<a-switch v-model="client.enable"></a-switch> <template slot="title">
</a-form-item> <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
<a-form-item label="password" v-if="inbound.protocol === Protocols.TROJAN"> </template>
<a-input v-model.trim="client.password"></a-input> <a-icon type="sync" @click="getNewEmail(client)"></a-icon>
</a-form-item> </a-tooltip>
<a-form-item label="id" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS"> </td>
<a-input v-model.trim="client.id"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS"> <a-input v-model.trim="client.email" style="width: 250px"></a-input>
<a-input type="number" v-model.number="client.alterId"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label="Subscription" v-if="client.email"> </tr>
<a-input v-model.trim="client.subId"></a-input> <tr v-if="inbound.protocol === Protocols.TROJAN">
</a-form-item> <td>password</td>
<a-form-item label="Telegram Username" v-if="client.email"> <td>
<a-input v-model.trim="client.tgId"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="client.password" style="width: 250px"></a-input>
<a-form-item v-if="inbound.xtls" label="flow"> </a-form-item>
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option value="">{{ i18n "none" }}</a-select-option> </tr>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
</a-select> <td>ID</td>
</a-form-item> <td>
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline"> <a-form-item>
<a-select v-model="client.flow" style="width: 150px"> <a-input v-model.trim="client.id" style="width: 250px"></a-input>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> </a-form-item>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> </td>
</a-select> </tr>
</a-form-item> <tr v-if="inbound.protocol === Protocols.VMESS">
<a-form-item> <td>{{ i18n "additional" }}</td>
<span slot="label"> <td>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <a-form-item>
<a-tooltip> <a-input-number v-model.number="client.alterId"></a-input-number>
<template slot="title"> </a-form-item>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> </td>
</template> </tr>
<a-icon type="question-circle" theme="filled"></a-icon> <tr v-if="client.email">
</a-tooltip> <td>Subscription</td>
</span> <td>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> <a-form-item>
<template v-if="isEdit && clientStats"> <a-input v-model.trim="client.subId" style="width: 250px"></a-input>
<span>{{ i18n "usage" }}:</span> </a-form-item>
<a-tag :color="statsColor"> </td>
[[ sizeFormat(clientStats.up) ]] / </tr>
[[ sizeFormat(clientStats.down) ]] <tr v-if="client.email">
([[ sizeFormat(clientStats.up + clientStats.down) ]]) <td>Telegram Username</td>
</a-tag> <td>
</template> <a-form-item>
</a-form-item> <a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
<a-form-item label="{{ i18n "pages.client.delayedStart" }}"> </a-form-item>
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch> </td>
</a-form-item> </tr>
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientModal.delayedStart"> <tr v-if="inbound.canEnableTlsFlow()">
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input> <td>Flow</td>
</a-form-item> <td>
<a-form-item v-else> <a-form-item>
<span slot="label"> <a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<span >{{ i18n "pages.inbounds.expireDate" }}</span> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-tooltip> <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
<template slot="title"> </a-select>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> </a-form-item>
</template> </td>
<a-icon type="question-circle" theme="filled"></a-icon> </tr>
</a-tooltip> <tr>
</span> <td>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" <a-tooltip>
v-model="client._expiryTime" style="width: 300px;"></a-date-picker> <template slot="title">
<a-tag color="red" v-if="isExpiry">Expired</a-tag> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</a-form-item> </template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="isEdit && clientStats">
<td>{{ i18n "usage" }}</td>
<td>
<a-tag :color="statsColor">
[[ sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="clientModal.delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,58 +1,91 @@
{{define "form/inbound"}} {{define "form/inbound"}}
<!-- base --> <!-- base -->
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "remark" }}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="dbInbound.remark"></a-input> <tr>
</a-form-item> <td>{{ i18n "enable" }}</td>
<a-form-item label='{{ i18n "enable" }}'> <td>
<a-switch v-model="dbInbound.enable"></a-switch> <a-form-item>
</a-form-item> <a-switch v-model="dbInbound.enable"></a-switch>
<a-form-item label='{{ i18n "protocol" }}'> </a-form-item>
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option> </tr>
</a-select> <tr>
</a-form-item> <td>{{ i18n "remark" }}</td>
<a-form-item> <td>
<span slot="label"> <a-form-item>
{{ i18n "monitor" }} <a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
<a-tooltip> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "protocol" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "monitor" }}
<a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span> <span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
</template> </template>
<a-icon type="question-circle" theme="filled"></a-icon> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</span> </td>
<a-input v-model.trim="inbound.listen"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> <a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
<a-input type="number" v-model.number="inbound.port"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item> </tr>
<span slot="label"> <tr>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <td>{{ i18n "pages.inbounds.port" }}</td>
<a-tooltip> <td>
<template slot="title"> <a-form-item>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <a-input-number v-model.number="inbound.port"></a-input-number>
</template> </a-form-item>
<a-icon type="question-circle" theme="filled"></a-icon> </td>
</a-tooltip> </tr>
</span> <tr>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> <td>
</a-form-item> <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-form-item> <a-tooltip>
<span slot="label"> <template slot="title">
<span >{{ i18n "pages.inbounds.expireDate" }}</span> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
<a-tooltip> </template>
<template slot="title"> <a-icon type="question-circle" theme="filled"></a-icon>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> </a-tooltip>
</template> </td>
<a-icon type="question-circle" theme="filled"></a-icon> <td>
</a-tooltip> <a-form-item>
</span> <a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" </a-form-item>
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" </td>
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker> </tr>
</a-form-item> <tr>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
<!-- vmess settings --> <!-- vmess settings -->

View File

@@ -1,20 +1,42 @@
{{define "form/dokodemo"}} {{define "form/dokodemo"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.settings.address"></a-input> <tr>
</a-form-item> <td>{{ i18n "pages.inbounds.targetAddress"}}</td>
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'> <td>
<a-input type="number" v-model.number="inbound.settings.port"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.settings.address"></a-input>
<a-form-item label='{{ i18n "pages.inbounds.network"}}'> </a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option value="tcp,udp">tcp+udp</a-select-option> </tr>
<a-select-option value="tcp">tcp</a-select-option> <tr>
<a-select-option value="udp">udp</a-select-option> <td>{{ i18n "pages.inbounds.destinationPort"}}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item label="FollowRedirect"> <a-input-number v-model.number="inbound.settings.port"></a-input-number>
<a-switch v-model="inbound.settings.followRedirect"></a-switch> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network"}}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>FollowRedirect</td>
<td>
<a-form-item>
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,10 +1,22 @@
{{define "form/http"}} {{define "form/http"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "username"}}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input> <tr>
</a-form-item> <td>{{ i18n "username"}}</td>
<a-form-item label='{{ i18n "password" }}'> <td>
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,19 +1,36 @@
{{define "form/shadowsocks"}} {{define "form/shadowsocks"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "encryption" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option> <td>{{ i18n "encryption" }}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-input v-model.trim="inbound.settings.password"></a-input> <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> </a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option value="tcp,udp">tcp+udp</a-select-option> </tr>
<a-select-option value="tcp">tcp</a-select-option> <tr>
<a-select-option value="udp">udp</a-select-option> <td>{{ i18n "password" }}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-input v-model.trim="inbound.settings.password"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,24 +1,50 @@
{{define "form/socks"}} {{define "form/socks"}}
<a-form layout="inline"> <a-form layout="inline">
<!-- <a-form-item label="Password authentication">--> <!-- <a-form-item label="Password authentication">-->
<a-form-item label='{{ i18n "password" }}'> <table width="100%" class="ant-table-tbody">
<a-switch :checked="inbound.settings.auth === 'password'" <tr>
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch> <td>{{ i18n "password" }}</td>
</a-form-item> <td>
<a-form-item>
<a-switch :checked="inbound.settings.auth === 'password'"
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
</a-form-item>
</td>
</tr>
<template v-if="inbound.settings.auth === 'password'"> <template v-if="inbound.settings.auth === 'password'">
<a-form-item label='{{ i18n "username" }}'> <tr>
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input> <td>{{ i18n "username" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "password" }}'> <a-form-item>
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input> <a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'> <tr>
<a-switch v-model="inbound.settings.udp"></a-switch> <td>{{ i18n "pages.inbounds.enable" }} udp</td>
</a-form-item> <td>
<a-form-item v-if="inbound.settings.udp" <a-form-item>
label="IP"> <a-switch v-model="inbound.settings.udp"></a-switch>
<a-input v-model.trim="inbound.settings.ip"></a-input> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>IP</td>
<td>
<a-form-item v-if="inbound.settings.udp">
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -2,9 +2,9 @@
<a-form layout="inline"> <a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}"> <a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
<a-form layout="inline"> <table width="100%" class="ant-table-tbody">
<a-form-item> <tr>
<span slot="label"> <td>
<span>{{ i18n "pages.inbounds.Email" }}</span> <span>{{ i18n "pages.inbounds.Email" }}</span>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
@@ -12,45 +12,88 @@
</template> </template>
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon> <a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
</a-tooltip> </a-tooltip>
</span> </td>
<a-input v-model.trim="client.email"></a-input> <td>
</a-form-item> <a-form-item>
</a-form> <a-input v-model.trim="client.email" style="width: 200px;"></a-input>
<a-form-item label="password"> </a-form-item>
<a-input v-model.trim="client.password"></a-input> </td>
</a-form-item> </tr>
<a-form-item v-if="inbound.xtls" label="flow"> <tr>
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <td>password</td>
<a-select-option value="">{{ i18n "none" }}</a-select-option> <td>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-form-item>
</a-select> <a-input v-model.trim="client.password" style="width: 200px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item> </td>
<span slot="label"> </tr>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <tr>
<a-tooltip> <td>Subscription</td>
<template slot="title"> <td>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <a-form-item v-if="client.email">
</template> <a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
<a-icon type="question-circle" theme="filled"></a-icon> </a-form-item>
</a-tooltip> </td>
</span> </tr>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> <tr>
</a-form-item> <td>Telegram Username</td>
<a-form-item> <td>
<span slot="label"> <a-form-item v-if="client.email">
<span >{{ i18n "pages.inbounds.expireDate" }}</span> <a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
<a-tooltip> </a-form-item>
<template slot="title"> </td>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> </tr>
</template> <tr>
<a-icon type="question-circle" theme="filled"></a-icon> <td>
</a-tooltip> <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
</span> <a-tooltip>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <template slot="title">
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
v-model="client._expiryTime" style="width: 300px;"></a-date-picker> </template>
</a-form-item> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<a-collapse v-else> <a-collapse v-else>
@@ -65,7 +108,7 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)"> <template v-if="inbound.isTcp && inbound.tls">
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-row> <a-row>
@@ -97,7 +140,7 @@
<a-input v-model="fallback.dest"></a-input> <a-input v-model="fallback.dest"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="xver"> <a-form-item label="xver">
<a-input type="number" v-model.number="fallback.xver"></a-input> <a-input-number v-model.number="fallback.xver"></a-input-number>
</a-form-item> </a-form-item>
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/> <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
</a-form> </a-form>

View File

@@ -2,61 +2,109 @@
<a-form layout="inline"> <a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}"> <a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
<a-form layout="inline"> <table width="100%" class="ant-table-tbody">
<a-form-item> <tr>
<span slot="label"> <td>
<span>{{ i18n "pages.inbounds.Email" }}</span> <span>{{ i18n "pages.inbounds.Email" }}</span>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span> <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
</template> </template>
<a-icon type="sync" @click="getNewEmail(client)"></a-icon> <a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
</a-tooltip> </a-tooltip>
</span> </td>
<a-input v-model.trim="client.email"></a-input> <td>
</a-form-item> <a-form-item>
</a-form> <a-input v-model.trim="client.email" style="width: 200px;"></a-input>
<a-form-item label="id"> </a-form-item>
<a-input v-model.trim="client.id"></a-input> </td>
</a-form-item> </tr>
<a-form-item v-if="inbound.xtls" label="flow"> <tr>
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <td>id</td>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <td>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-form-item>
</a-select> <a-input v-model.trim="client.id" style="width: 200px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline"> </td>
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </tr>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <tr v-if="inbound.canEnableTlsFlow()">
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <td>flow</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item> <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<span slot="label"> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
<a-tooltip> </a-select>
<template slot="title"> </a-form-item>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> </td>
</template> </tr>
<a-icon type="question-circle" theme="filled"></a-icon> <tr>
</a-tooltip> <td>Subscription</td>
</span> <td>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> <a-form-item v-if="client.email">
</a-form-item> <a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
<a-form-item> </a-form-item>
<span slot="label"> </td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span> </tr>
<a-tooltip> <tr>
<template slot="title"> <td>Telegram Username</td>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> <td>
</template> <a-form-item v-if="client.email">
<a-icon type="question-circle" theme="filled"></a-icon> <a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
</a-tooltip> </a-form-item>
</span> </td>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" </tr>
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" <tr>
v-model="client._expiryTime" style="width: 300px;"></a-date-picker> <td>
</a-form-item> <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<a-collapse v-else> <a-collapse v-else>
@@ -71,7 +119,7 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)"> <template v-if="inbound.isTcp && inbound.tls">
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-row> <a-row>
@@ -103,7 +151,7 @@
<a-input v-model="fallback.dest"></a-input> <a-input v-model="fallback.dest"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="xver"> <a-form-item label="xver">
<a-input type="number" v-model.number="fallback.xver"></a-input> <a-input-number v-model.number="fallback.xver"></a-input-number>
</a-form-item> </a-form-item>
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/> <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
</a-form> </a-form>

View File

@@ -2,9 +2,9 @@
<a-form layout="inline"> <a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}"> <a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
<a-form layout="inline"> <table width="100%" class="ant-table-tbody">
<a-form-item> <tr>
<span slot="label"> <td>
<span>{{ i18n "pages.inbounds.Email" }}</span> <span>{{ i18n "pages.inbounds.Email" }}</span>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
@@ -12,42 +12,96 @@
</template> </template>
<a-icon type="sync" @click="getNewEmail(client)"></a-icon> <a-icon type="sync" @click="getNewEmail(client)"></a-icon>
</a-tooltip> </a-tooltip>
</span> </td>
<a-input v-model.trim="client.email"></a-input> <td>
</a-form-item> <a-form-item>
</a-form> <a-input v-model.trim="client.email" style="width: 200px;"></a-input>
<a-form-item label="id"> </a-form-item>
<a-input v-model.trim="client.id"></a-input> </td>
</a-form-item> </tr>
<a-form-item label='{{ i18n "additional" }} ID'> <tr>
<a-input type="number" v-model.number="client.alterId"></a-input> <td>id</td>
</a-form-item> <td>
<a-form-item> <a-form-item>
<span slot="label"> <a-input v-model.trim="client.id" style="width: 200px;"></a-input>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) </a-form-item>
<a-tooltip> </td>
<template slot="title"> </tr>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <tr>
</template> <td>{{ i18n "additional" }}</td>
<a-icon type="question-circle" theme="filled"></a-icon> <td>
</a-tooltip> <a-form-item>
</span> <a-input-number v-model.number="client.alterId"></a-input-number>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> </a-form-item>
</a-form-item> </td>
<a-form-item> </tr>
<span slot="label"> <tr>
<span >{{ i18n "pages.inbounds.expireDate" }}</span> <td>Subscription</td>
<a-tooltip> <td>
<template slot="title"> <a-form-item v-if="client.email">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> <a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
</template> </a-form-item>
<a-icon type="question-circle" theme="filled"></a-icon> </td>
</a-tooltip> </tr>
</span> <tr>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <td>Telegram Username</td>
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" <td>
v-model="client._expiryTime" style="width: 300px;"></a-date-picker> <a-form-item v-if="client.email">
</a-form-item> <a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<a-collapse v-else> <a-collapse v-else>
@@ -64,8 +118,7 @@
</a-collapse> </a-collapse>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'> <a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch> <a-switch v-model="inbound.settings.disableInsecure"></a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,7 +1,14 @@
{{define "form/streamGRPC"}} {{define "form/streamGRPC"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="serviceName"> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input> <tr>
</a-form-item> <td>serviceName</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,12 +1,24 @@
{{define "form/streamHTTP"}} {{define "form/streamHTTP"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "path" }}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.stream.http.path"></a-input> <tr>
</a-form-item> <td>{{ i18n "path" }}</td>
<a-form-item label="host"> <td>
<a-row v-for="(host, index) in inbound.stream.http.host"> <a-form-item>
<a-input v-model.trim="inbound.stream.http.host[index]"></a-input> <a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
</a-row> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>host</td>
<td>
<a-form-item>
<a-row v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
</a-row>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,38 +1,85 @@
{{define "form/streamKCP"}} {{define "form/streamKCP"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "camouflage" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option value="none">nonenot camouflage</a-select-option> <td>{{ i18n "camouflage" }}</td>
<a-select-option value="srtp">srtpcamouflage video call</a-select-option> <td>
<a-select-option value="utp">utpcamouflage BT download</a-select-option> <a-form-item>
<a-select-option value="wechat-video">wechat-videocamouflage WeChat video</a-select-option> <a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="dtls">dtlscamouflage DTLS 1.2 packages</a-select-option> <a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="wireguard">wireguardcamouflage wireguard packages</a-select-option> <a-select-option value="srtp">srtp (video call)</a-select-option>
</a-select> <a-select-option value="utp">utp (BT download)</a-select-option>
</a-form-item> <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-form-item label='{{ i18n "password" }}'> <a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-input v-model.number="inbound.stream.kcp.seed"></a-input> <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-form-item> </a-select>
<a-form-item label="mtu"> </a-form-item>
<a-input type="number" v-model.number="inbound.stream.kcp.mtu"></a-input> </td>
</a-form-item> </tr>
<a-form-item label="tti (ms)"> <tr>
<a-input type="number" v-model.number="inbound.stream.kcp.tti"></a-input> <td>{{ i18n "password" }}</td>
</a-form-item> <td>
<a-form-item label="uplink capacity (MB/S)"> <a-form-item>
<a-input type="number" v-model.number="inbound.stream.kcp.upCap"></a-input> <a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="downlink capacity (MB/S)"> </td>
<a-input type="number" v-model.number="inbound.stream.kcp.downCap"></a-input> </tr>
</a-form-item> <tr>
<a-form-item label="congestion"> <td>mtu</td>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch> <td>
</a-form-item> <a-form-item>
<a-form-item label="read buffer size (MB)"> <a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
<a-input type="number" v-model.number="inbound.stream.kcp.readBuffer"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label="write buffer size (MB)"> </tr>
<a-input type="number" v-model.number="inbound.stream.kcp.writeBuffer"></a-input> <tr>
</a-form-item> <td>tti (ms)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>uplink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>downlink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>congestion</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>read buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>write buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,24 +1,41 @@
{{define "form/streamQUIC"}} {{define "form/streamQUIC"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option value="none">none</a-select-option> <td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option> <td>
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option> <a-form-item>
</a-select> <a-select v-model="inbound.stream.quic.security" style="width: 200px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
</a-form-item> <a-select-option value="none">none</a-select-option>
<a-form-item label='{{ i18n "password" }}'> <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
<a-input v-model.trim="inbound.stream.quic.key"></a-input> <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
</a-form-item> </a-select>
<a-form-item label='{{ i18n "camouflage" }}'> </a-form-item>
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option value="none">nonenot camouflage</a-select-option> </tr>
<a-select-option value="srtp">srtpcamouflage video call</a-select-option> <tr>
<a-select-option value="utp">utpcamouflage BT download</a-select-option> <td>{{ i18n "password" }}</td>
<a-select-option value="wechat-video">wechat-videocamouflage WeChat video</a-select-option> <td>
<a-select-option value="dtls">dtlscamouflage DTLS 1.2 packages</a-select-option> <a-form-item>
<a-select-option value="wireguard">wireguardcamouflage wireguard packages</a-select-option> <a-input v-model.trim="inbound.stream.quic.key" style="width: 200px;"></a-input>
</a-select> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.quic.type" style="width: 200px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,16 +1,24 @@
{{define "form/streamSettings"}} {{define "form/streamSettings"}}
<!-- select stream network --> <!-- select stream network -->
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "transmission" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option value="tcp">tcp</a-select-option> <td>{{ i18n "transmission" }}</td>
<a-select-option value="kcp">kcp</a-select-option> <td>
<a-select-option value="ws">ws</a-select-option> <a-form-item>
<a-select-option value="http">http</a-select-option> <a-select v-model="inbound.stream.network" @change="streamNetworkChange"
<a-select-option value="quic">quic</a-select-option> :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="grpc">grpc</a-select-option> <a-select-option value="tcp">tcp</a-select-option>
</a-select> <a-select-option value="kcp">kcp</a-select-option>
</a-form-item> <a-select-option value="ws">ws</a-select-option>
<a-select-option value="http">http</a-select-option>
<a-select-option value="quic">quic</a-select-option>
<a-select-option value="grpc">grpc</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
<!-- tcp --> <!-- tcp -->

View File

@@ -13,74 +13,109 @@
</a-form> </a-form>
<!-- tcp request --> <!-- tcp request -->
<a-form v-if="inbound.stream.tcp.type === 'http'" <a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
layout="inline"> <table width="100%" class="ant-table-tbody">
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'> <tr>
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input> <td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}'> <a-form-item>
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input> <a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestPath" }}'> </td>
<a-row v-for="(path, index) in inbound.stream.tcp.request.path"> </tr>
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input> <tr>
</a-row> <td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'> <a-form-item>
<a-row> <a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
<a-button size="small" </a-form-item>
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')"> </td>
+ </tr>
</a-button> <tr>
</a-row> <td>{{ i18n "pages.inbounds.stream.tcp.requestPath" }}</td>
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers"> <td>
<a-input style="width: 50%" v-model.trim="header.name" <a-form-item>
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input> <a-row v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input style="width: 50%" v-model.trim="header.value" <a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;"></a-input>
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> </a-row>
<template slot="addonAfter"> </a-form-item>
<a-button size="small" </td>
@click="inbound.stream.tcp.request.removeHeader(index)"> </tr>
- <tr>
</a-button> <td colspan="2">
</template> <a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
</a-input> <a-row>
</a-input-group> <a-button size="small"
</a-form-item> @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
</a-form> +
</a-button>
<!-- tcp response --> </a-row>
<a-form v-if="inbound.stream.tcp.type === 'http'" <a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
layout="inline"> <a-input style="width: 50%" v-model.trim="header.name"
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'> addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input> <a-input style="width: 50%" v-model.trim="header.value"
</a-form-item> addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}'> <template slot="addonAfter">
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input> <a-button size="small"
</a-form-item> @click="inbound.stream.tcp.request.removeHeader(index)">
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'> -
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input> </a-button>
</a-form-item> </template>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'> </a-input>
<a-row> </a-input-group>
<a-button size="small" </a-form-item>
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"> </td>
+ </tr>
</a-button> <!-- tcp response -->
</a-row> <tr>
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers"> <td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
<a-input style="width: 50%" v-model.trim="header.name" <td>
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input> <a-form-item>
<a-input style="width: 50%" v-model.trim="header.value" <a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> </a-form-item>
<template slot="addonAfter"> </td>
<a-button size="small" </tr>
@click="inbound.stream.tcp.response.removeHeader(index)"> <tr>
- <td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
</a-button> <td>
</template> <a-form-item>
</a-input> <a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
</a-input-group> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
<a-row>
<a-button size="small"
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
+
</a-button>
</a-row>
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input style="width: 50%" v-model.trim="header.name"
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
<a-input style="width: 50%" v-model.trim="header.value"
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small"
@click="inbound.stream.tcp.response.removeHeader(index)">
-
</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,33 +1,47 @@
{{define "form/streamWS"}} {{define "form/streamWS"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="acceptProxyProtocol"> <table width="100%" class="ant-table-tbody">
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch> <tr>
</a-form-item> <td>acceptProxyProtocol</td>
</a-form> <td>
<a-form layout="inline"> <a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
<a-input v-model.trim="inbound.stream.ws.path"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'> </tr>
<a-row> <tr>
<a-button size="small" <td>{{ i18n "path" }}</td>
@click="inbound.stream.ws.addHeader('Host', '')"> <td>
+ <a-form-item>
</a-button> <a-input v-model.trim="inbound.stream.ws.path" style="width: 250px;"></a-input>
</a-row> </a-form-item>
<a-input-group v-for="(header, index) in inbound.stream.ws.headers"> </td>
<a-input style="width: 50%" v-model.trim="header.name" </tr>
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input> <tr>
<a-input style="width: 50%" v-model.trim="header.value" <td colspan="2">
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
<template slot="addonAfter"> <a-row>
<a-button size="small" <a-button size="small"
@click="inbound.stream.ws.removeHeader(index)"> @click="inbound.stream.ws.addHeader('Host', '')">
- +
</a-button> </a-button>
</template> </a-row>
</a-input> <a-input-group v-for="(header, index) in inbound.stream.ws.headers">
</a-input-group> <a-input style="width: 50%" v-model.trim="header.name"
</a-form-item> addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
<a-input style="width: 50%" v-model.trim="header.value"
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small"
@click="inbound.stream.ws.removeHeader(index)">
-
</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,75 +1,241 @@
{{define "form/tlsSettings"}} {{define "form/tlsSettings"}}
<!-- tls enable --> <!-- tls enable -->
<a-form layout="inline" v-if="inbound.canSetTls()"> <a-form v-if="inbound.canSetTls()" layout="inline">
<a-form-item label="tls"> <a-form-item label="TLS">
<a-switch v-model="inbound.tls"> <a-switch v-model="inbound.tls">
</a-switch> </a-switch>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.canEnableXTls()" label="xtls"> <a-form-item v-if="inbound.canEnableReality()" label="Reality">
<a-switch v-model="inbound.xtls"></a-switch> <a-switch v-model="inbound.reality"></a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- tls settings --> <!-- tls settings -->
<a-form v-if="inbound.tls || inbound.xtls" layout="inline"> <a-form v-if="inbound.tls" layout="inline">
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls"> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input> <tr>
</a-form-item> <td>SNI</td>
<a-form-item label="CipherSuites"> <td>
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px"> <a-form-item placeholder="Server Name Indication" v-if="inbound.tls">
<a-select-option value="">auto</a-select-option> <a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option> </a-form-item>
</a-select> </td>
</a-form-item> </tr>
<a-form-item label="MinVersion"> <tr>
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <td>CipherSuites</td>
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> <td>
</a-select> <a-form-item>
</a-form-item> <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-form-item label="MaxVersion"> <a-select-option value="">auto</a-select-option>
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> </a-select>
</a-select> </a-form-item>
</a-form-item> </td>
<a-form-item label="uTLS" v-if="inbound.tls" > </tr>
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px"> <tr>
<a-select-option value=''>None</a-select-option> <td>MinVersion</td>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <td>
</a-select> <a-form-item>
</a-form-item> <a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-form-item label='{{ i18n "domainName" }}'> <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
<a-input v-model.trim="inbound.stream.tls.server"></a-input> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Alpn"> </td>
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px"> </tr>
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox> <tr>
</a-checkbox-group> <td>MaxVersion</td>
</a-form-item> <td>
<a-form-item label="Allow insecure"> <a-form-item>
<a-switch v-model="inbound.stream.tls.settings[0].allowInsecure"></a-switch> <a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
</a-form-item> <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
<a-form-item label='{{ i18n "certificate" }}'> </a-select>
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid"> </a-form-item>
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button> </td>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button> </tr>
</a-radio-group> <tr>
</a-form-item> <td>uTLS</td>
<template v-if="inbound.stream.tls.certs[0].useFile"> <td>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'> <a-form-item>
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:300px;"></a-input> <a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px">
</a-form-item> <a-select-option value=''>None</a-select-option>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input> </a-select>
</a-form-item> </a-form-item>
<a-button @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button> </td>
</tr>
<tr>
<td>{{ i18n "domainName" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Alpn</td>
<td>
<a-form-item>
<a-checkbox-group v-model="inbound.stream.tls.alpn">
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
</a-checkbox-group>
</a-form-item>
</td>
</tr>
<tr>
<td>Allow insecure</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-form-item label="{{ i18n "certificate" }}">
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
</a-form-item>
</td>
</tr>
<template v-if="inbound.stream.tls.certs[0].useFile">
<tr>
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</td>
</tr>
</template> </template>
<template v-else> <template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'> <tr>
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].cert"></a-input> <td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'> <a-form-item>
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].key"></a-input> <a-input type="textarea" :rows="3" style="width:250px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
</table>
</a-form>
<!-- reality settings -->
<a-form v-if="inbound.reality" layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "domainName" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Show</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Xver</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>uTLS</td>
<td>
<a-form-item >
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 250px">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Dest</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Server Names</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Short Ids</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>SpiderX</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Private Key</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Public Key</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -49,9 +49,9 @@
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td> </td>
<td v-else-if="inbound.xtls"> <td v-else-if="inbound.reality">
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td> </td>
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag> <td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
</td> </td>

View File

@@ -43,6 +43,14 @@
loading(loading) { loading(loading) {
inModal.confirmLoading = loading; inModal.confirmLoading = loading;
}, },
getClients(protocol, clientSettings) {
switch(protocol){
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
default: return null;
}
},
}; };
const protocols = { const protocols = {
@@ -62,6 +70,7 @@
inModal: inModal, inModal: inModal,
Protocols: protocols, Protocols: protocols,
SSMethods: SSMethods, SSMethods: SSMethods,
delayedStart: false,
get inbound() { get inbound() {
return inModal.inbound; return inModal.inbound;
}, },
@@ -70,36 +79,40 @@
}, },
get isEdit() { get isEdit() {
return inModal.isEdit; return inModal.isEdit;
} },
get client() {
return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0];
},
get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
},
set delayedExpireDays(days){
this.client.expiryTime = -86400000 * days;
},
}, },
methods: { methods: {
streamNetworkChange(oldValue) { streamNetworkChange() {
if (oldValue === 'kcp') { if (!inModal.inbound.canSetTls()) {
this.inModal.inbound.tls = false; this.inModal.inbound.stream.security = 'none';
} }
}, if (!inModal.inbound.canEnableReality()) {
addClient(protocol, clients) { this.inModal.inbound.reality = false;
switch (protocol) {
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
default: return null;
} }
}, },
removeClient(index, clients) {
clients.splice(index, 1);
},
isExpiry(index) {
return this.inbound.isExpiry(index)
},
isClientEnable(email) {
clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null
return clientStats ? clientStats['enable'] : true
},
setDefaultCertData(){ setDefaultCertData(){
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert; inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey; inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
}, },
async getNewX25519Cert(){
inModal.loading(true);
const msg = await HttpUtil.post('/server/getNewX25519Cert');
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
},
getNewEmail(client) { getNewEmail(client) {
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
var string = ''; var string = '';

View File

@@ -132,7 +132,7 @@
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS"> <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag> <a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">tls</a-tag> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">tls</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXTls" color="cyan">xtls</a-tag> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">reality</a-tag>
</template> </template>
</template> </template>
<template slot="clients" slot-scope="text, dbInbound"> <template slot="clients" slot-scope="text, dbInbound">
@@ -209,11 +209,10 @@
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
<script> <script>
const columns = [{ const columns = [{
title: '{{ i18n "pages.inbounds.operate" }}', title: '{{ i18n "pages.inbounds.operate" }}',
align: 'center', align: 'center',
width: 30, width: 40,
scopedSlots: { customRender: 'action' }, scopedSlots: { customRender: 'action' },
}, { }, {
title: '{{ i18n "pages.inbounds.enable" }}', title: '{{ i18n "pages.inbounds.enable" }}',
@@ -221,7 +220,7 @@
width: 40, width: 40,
scopedSlots: { customRender: 'enable' }, scopedSlots: { customRender: 'enable' },
}, { }, {
title: "Id", title: "ID",
align: 'center', align: 'center',
dataIndex: "id", dataIndex: "id",
width: 30, width: 30,
@@ -529,9 +528,9 @@
title: '{{ i18n "pages.client.add"}}', title: '{{ i18n "pages.client.add"}}',
okText: '{{ i18n "pages.client.submitAdd"}}', okText: '{{ i18n "pages.client.submitAdd"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
confirm: async (inbound, dbInbound, index) => { confirm: async (clients, dbInboundId) => {
clientModal.loading(); clientModal.loading();
await this.addClient(inbound, dbInbound); await this.addClient(clients, dbInboundId);
clientModal.close(); clientModal.close();
}, },
isEdit: false isEdit: false
@@ -543,9 +542,9 @@
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark, title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
okText: '{{ i18n "pages.client.bulk"}}', okText: '{{ i18n "pages.client.bulk"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
confirm: async (inbound, dbInbound) => { confirm: async (clients, dbInboundId) => {
clientsBulkModal.loading(); clientsBulkModal.loading();
await this.addClient(inbound, dbInbound); await this.addClient(clients, dbInboundId);
clientsBulkModal.close(); clientsBulkModal.close();
}, },
}); });
@@ -559,9 +558,9 @@
okText: '{{ i18n "pages.client.submitEdit"}}', okText: '{{ i18n "pages.client.submitEdit"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
index: index, index: index,
confirm: async (inbound, dbInbound, index) => { confirm: async (client, dbInboundId, index) => {
clientModal.loading(); clientModal.loading();
await this.updateClient(inbound, dbInbound, index); await this.updateClient(client, dbInboundId, index);
clientModal.close(); clientModal.close();
}, },
isEdit: true isEdit: true
@@ -571,17 +570,17 @@
firstKey = Object.keys(client)[0]; firstKey = Object.keys(client)[0];
return clients.findIndex(c => c[firstKey] === client[firstKey]); return clients.findIndex(c => c[firstKey] === client[firstKey]);
}, },
async addClient(inbound, dbInbound) { async addClient(clients, dbInboundId) {
const data = { const data = {
id: dbInbound.id, id: dbInboundId,
settings: inbound.settings.toString(), settings: '{"clients": [' + clients.toString() +']}',
}; };
await this.submit('/xui/inbound/addClient/', data); await this.submit(`/xui/inbound/addClient`, data);
}, },
async updateClient(inbound, dbInbound, index) { async updateClient(client, dbInboundId, index) {
const data = { const data = {
id: dbInbound.id, id: dbInboundId,
settings: inbound.settings.toString(), settings: '{"clients": [' + client.toString() +']}',
}; };
await this.submit(`/xui/inbound/updateClient/${index}`, data); await this.submit(`/xui/inbound/updateClient/${index}`, data);
}, },
@@ -656,8 +655,8 @@
inbound = dbInbound.toInbound(); inbound = dbInbound.toInbound();
clients = this.getClients(dbInbound.protocol, inbound.settings); clients = this.getClients(dbInbound.protocol, inbound.settings);
index = this.findIndexOfClient(clients, client); index = this.findIndexOfClient(clients, client);
clients[index].enable = ! clients[index].enable clients[index].enable = !clients[index].enable;
await this.updateClient(inbound, dbInbound, index); await this.updateClient(clients[index],dbInboundId, index);
this.loading(false); this.loading(false);
}, },
async submit(url, data) { async submit(url, data) {

View File

@@ -53,7 +53,7 @@
</a-col> </a-col>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<temlate> <template>
<a-select <a-select
ref="selectLang" ref="selectLang"
v-model="lang" v-model="lang"
@@ -66,7 +66,7 @@
&nbsp;&nbsp;<span v-text="l.name"></span> &nbsp;&nbsp;<span v-text="l.name"></span>
</a-select-option> </a-select-option>
</a-select> </a-select>
</temlate> </template>
</a-col> </a-col>
</a-row> </a-row>

View File

@@ -64,28 +64,45 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
return clients, nil return clients, nil
} }
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) { func (s *InboundService) getAllEmails() ([]string, error) {
db := database.GetDB() db := database.GetDB()
var inbounds []*model.Inbound var emails []string
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan}) err := db.Raw(`
if ignoreId > 0 { SELECT JSON_EXTRACT(client.value, '$.email')
db = db.Where("id != ?", ignoreId) FROM inbounds,
} JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
db = db.Find(&inbounds) `).Scan(&emails).Error
if db.Error != nil {
return "", db.Error
}
for _, inbound := range inbounds { if err != nil {
clients, err := s.getClients(inbound) return nil, err
if err != nil { }
return "", err return emails, nil
}
func (s *InboundService) contains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
} }
}
return false
}
for _, client := range clients { func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
if emails[client.Email] { allEmails, err := s.getAllEmails()
if err != nil {
return "", err
}
var emails []string
for _, client := range clients {
if client.Email != "" {
if s.contains(emails, client.Email) {
return client.Email, nil return client.Email, nil
} }
if s.contains(allEmails, client.Email) {
return client.Email, nil
}
emails = append(emails, client.Email)
} }
} }
return "", nil return "", nil
@@ -96,16 +113,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
if err != nil { if err != nil {
return "", err return "", err
} }
emails := make(map[string]bool) allEmails, err := s.getAllEmails()
if err != nil {
return "", err
}
var emails []string
for _, client := range clients { for _, client := range clients {
if client.Email != "" { if client.Email != "" {
if emails[client.Email] { if s.contains(emails, client.Email) {
return client.Email, nil return client.Email, nil
} }
emails[client.Email] = true if s.contains(allEmails, client.Email) {
return client.Email, nil
}
emails = append(emails, client.Email)
} }
} }
return s.checkEmailsExist(emails, inbound.Id) return "", nil
} }
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) { func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
@@ -201,14 +225,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
return inbound, common.NewError("Port already exists:", inbound.Port) return inbound, common.NewError("Port already exists:", inbound.Port)
} }
existEmail, err := s.checkEmailExistForInbound(inbound)
if err != nil {
return inbound, err
}
if existEmail != "" {
return inbound, common.NewError("Duplicate email:", existEmail)
}
oldInbound, err := s.GetInbound(inbound.Id) oldInbound, err := s.GetInbound(inbound.Id)
if err != nil { if err != nil {
return inbound, err return inbound, err
@@ -231,39 +247,53 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
return inbound, db.Save(oldInbound).Error return inbound, db.Save(oldInbound).Error
} }
func (s *InboundService) AddInboundClient(inbound *model.Inbound) error { func (s *InboundService) AddInboundClient(data *model.Inbound) error {
existEmail, err := s.checkEmailExistForInbound(inbound) clients, err := s.getClients(data)
if err != nil { if err != nil {
return err return err
} }
var settings map[string]interface{}
err = json.Unmarshal([]byte(data.Settings), &settings)
if err != nil {
return err
}
interfaceClients := settings["clients"].([]interface{})
existEmail, err := s.checkEmailsExistForClients(clients)
if err != nil {
return err
}
if existEmail != "" { if existEmail != "" {
return common.NewError("Duplicate email:", existEmail) return common.NewError("Duplicate email:", existEmail)
} }
clients, err := s.getClients(inbound) oldInbound, err := s.GetInbound(data.Id)
if err != nil { if err != nil {
return err return err
} }
oldInbound, err := s.GetInbound(inbound.Id) var oldSettings map[string]interface{}
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
if err != nil { if err != nil {
return err return err
} }
oldClients, err := s.getClients(oldInbound) oldClients := oldSettings["clients"].([]interface{})
oldClients = append(oldClients, interfaceClients...)
oldSettings["clients"] = oldClients
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
if err != nil { if err != nil {
return err return err
} }
oldInbound.Settings = inbound.Settings oldInbound.Settings = string(newSettings)
if len(clients[len(clients)-1].Email) > 0 { for _, client := range clients {
s.AddClientStat(inbound.Id, &clients[len(clients)-1]) if len(client.Email) > 0 {
} s.AddClientStat(data.Id, &client)
for i := len(oldClients); i < len(clients); i++ {
if len(clients[i].Email) > 0 {
s.AddClientStat(inbound.Id, &clients[i])
} }
} }
db := database.GetDB() db := database.GetDB()
@@ -289,21 +319,21 @@ func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string)
return db.Save(oldInbound).Error return db.Save(oldInbound).Error
} }
func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error { func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
existEmail, err := s.checkEmailExistForInbound(inbound) clients, err := s.getClients(data)
if err != nil {
return err
}
if existEmail != "" {
return common.NewError("Duplicate email:", existEmail)
}
clients, err := s.getClients(inbound)
if err != nil { if err != nil {
return err return err
} }
oldInbound, err := s.GetInbound(inbound.Id) var settings map[string]interface{}
err = json.Unmarshal([]byte(data.Settings), &settings)
if err != nil {
return err
}
inerfaceClients := settings["clients"].([]interface{})
oldInbound, err := s.GetInbound(data.Id)
if err != nil { if err != nil {
return err return err
} }
@@ -313,18 +343,43 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
return err return err
} }
oldInbound.Settings = inbound.Settings if len(clients[0].Email) > 0 && clients[0].Email != oldClients[index].Email {
existEmail, err := s.checkEmailsExistForClients(clients)
if err != nil {
return err
}
if existEmail != "" {
return common.NewError("Duplicate email:", existEmail)
}
}
var oldSettings map[string]interface{}
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
if err != nil {
return err
}
settingsClients := oldSettings["clients"].([]interface{})
settingsClients[index] = inerfaceClients[0]
oldSettings["clients"] = settingsClients
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
if err != nil {
return err
}
oldInbound.Settings = string(newSettings)
db := database.GetDB() db := database.GetDB()
if len(clients[index].Email) > 0 { if len(clients[0].Email) > 0 {
if len(oldClients[index].Email) > 0 { if len(oldClients[index].Email) > 0 {
err = s.UpdateClientStat(oldClients[index].Email, &clients[index]) err = s.UpdateClientStat(oldClients[index].Email, &clients[0])
if err != nil { if err != nil {
return err return err
} }
} else { } else {
s.AddClientStat(inbound.Id, &clients[index]) s.AddClientStat(data.Id, &clients[0])
} }
} else { } else {
err = s.DelClientStat(db, oldClients[index].Email) err = s.DelClientStat(db, oldClients[index].Email)

View File

@@ -194,9 +194,11 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
versions := make([]string, 0, len(releases)) var versions []string
for _, release := range releases { for _, release := range releases {
versions = append(versions, release.TagName) if release.TagName >= "v1.8.0" {
versions = append(versions, release.TagName)
}
} }
return versions, nil return versions, nil
} }
@@ -390,3 +392,29 @@ func (s *ServerService) GetDb() ([]byte, error) {
return fileContents, nil return fileContents, nil
} }
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
// Run the command
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
privateKeyLine := strings.Split(lines[0], ":")
publicKeyLine := strings.Split(lines[1], ":")
privateKey := strings.TrimSpace(privateKeyLine[1])
publicKey := strings.TrimSpace(publicKeyLine[1])
keyPair := map[string]interface{}{
"privateKey": privateKey,
"publicKey": publicKey,
}
return keyPair, nil
}

View File

@@ -8,6 +8,7 @@ import (
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
"x-ui/xray"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"gorm.io/gorm" "gorm.io/gorm"
@@ -18,12 +19,15 @@ type SubService struct {
inboundService InboundService inboundService InboundService
} }
func (s *SubService) GetSubs(subId string, host string) ([]string, error) { func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
s.address = host s.address = host
var result []string var result []string
var header string
var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId) inbounds, err := s.getInboundsBySubId(subId)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.inboundService.getClients(inbound) clients, err := s.inboundService.getClients(inbound)
@@ -37,22 +41,60 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
if client.SubID == subId { if client.SubID == subId {
link := s.getLink(inbound, client.Email) link := s.getLink(inbound, client.Email)
result = append(result, link) result = append(result, link)
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
} }
} }
} }
return result, nil for index, clientTraffic := range clientTraffics {
if index == 0 {
traffic.Up = clientTraffic.Up
traffic.Down = clientTraffic.Down
traffic.Total = clientTraffic.Total
if clientTraffic.ExpiryTime > 0 {
traffic.ExpiryTime = clientTraffic.ExpiryTime
}
} else {
traffic.Up += clientTraffic.Up
traffic.Down += clientTraffic.Down
if traffic.Total == 0 || clientTraffic.Total == 0 {
traffic.Total = 0
} else {
traffic.Total += clientTraffic.Total
}
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
traffic.ExpiryTime = 0
}
}
}
header = fmt.Sprintf("upload=%d;download=%d", traffic.Up, traffic.Down)
if traffic.Total > 0 {
header = header + fmt.Sprintf(";total=%d", traffic.Total)
}
if traffic.ExpiryTime > 0 {
header = header + fmt.Sprintf(";expire=%d", traffic.ExpiryTime)
}
return result, header, nil
} }
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
db := database.GetDB() db := database.GetDB()
var inbounds []*model.Inbound var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound { if err != nil && err != gorm.ErrRecordNotFound {
return nil, err return nil, err
} }
return inbounds, nil return inbounds, nil
} }
func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email string) xray.ClientTraffic {
for _, traffic := range traffics {
if traffic.Email == email {
return traffic
}
}
return xray.ClientTraffic{}
}
func (s *SubService) getLink(inbound *model.Inbound, email string) string { func (s *SubService) getLink(inbound *model.Inbound, email string) string {
switch inbound.Protocol { switch inbound.Protocol {
case "vmess": case "vmess":
@@ -269,29 +311,35 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
} }
} }
if security == "xtls" { if security == "reality" {
params["security"] = "xtls" params["security"] = "reality"
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{}) realitySetting, _ := stream["realitySettings"].(map[string]interface{})
alpns, _ := xtlsSetting["alpn"].([]interface{}) realitySettings, _ := searchKey(realitySetting, "settings")
var alpn []string if realitySetting != nil {
for _, a := range alpns { if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
alpn = append(alpn, a.(string)) sNames, _ := sniValue.([]interface{})
} params["sni"], _ = sNames[0].(string)
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil {
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
} }
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok { if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["fp"], _ = fpValue.(string) params["pbk"], _ = pbkValue.(string)
} }
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok { if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
if insecure.(bool) { shortIds, _ := sidValue.([]interface{})
params["allowInsecure"] = "1" params["sid"], _ = shortIds[0].(string)
}
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp
}
}
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
params["spx"] = spx
}
}
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
if sname, ok := serverName.(string); ok && len(sname) > 0 {
address = sname
} }
} }
} }
@@ -299,11 +347,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow params["flow"] = clients[clientIndex].Flow
} }
serverName, _ := xtlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
} }
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port) link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
@@ -413,29 +456,35 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
} }
} }
if security == "xtls" { if security == "reality" {
params["security"] = "xtls" params["security"] = "reality"
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{}) realitySetting, _ := stream["realitySettings"].(map[string]interface{})
alpns, _ := xtlsSetting["alpn"].([]interface{}) realitySettings, _ := searchKey(realitySetting, "settings")
var alpn []string if realitySetting != nil {
for _, a := range alpns { if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
alpn = append(alpn, a.(string)) sNames, _ := sniValue.([]interface{})
} params["sni"], _ = sNames[0].(string)
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil {
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
} }
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok { if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["fp"], _ = fpValue.(string) params["pbk"], _ = pbkValue.(string)
} }
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok { if sidValue, ok := searchKey(realitySettings, "shortIds"); ok {
if insecure.(bool) { shortIds, _ := sidValue.([]interface{})
params["allowInsecure"] = "1" params["sid"], _ = shortIds[0].(string)
}
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp
}
}
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
params["spx"] = spx
}
}
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
if sname, ok := serverName.(string); ok && len(sname) > 0 {
address = sname
} }
} }
} }
@@ -443,11 +492,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow params["flow"] = clients[clientIndex].Flow
} }
serverName, _ := xtlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
} }
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port) link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)

View File

@@ -167,7 +167,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
case "client_traffic": case "client_traffic":
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName) t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
case "client_commands": case "client_commands":
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.") t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Password]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
case "commands": case "commands":
t.SendMsgToTgbot(callbackQuery.From.ID, "Search for a client email:\r\n<code>/usage email</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [remark]</code>") t.SendMsgToTgbot(callbackQuery.From.ID, "Search for a client email:\r\n<code>/usage email</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [remark]</code>")
} }

View File

@@ -39,9 +39,9 @@
"depleted" = "Depleted" "depleted" = "Depleted"
"depletingSoon" = "Depleting soon" "depletingSoon" = "Depleting soon"
"domainName" = "Domain name" "domainName" = "Domain name"
"additional" = "Alter" "additional" = "Alter ID"
"monitor" = "Listen IP" "monitor" = "Listen IP"
"certificate" = "Certificat" "certificate" = "Certificate"
"fail" = "Fail" "fail" = "Fail"
"success" = " Success" "success" = " Success"
"getVersion" = "Get version" "getVersion" = "Get version"
@@ -106,8 +106,8 @@
"expireDate" = "Expire date" "expireDate" = "Expire date"
"resetTraffic" = "Reset traffic" "resetTraffic" = "Reset traffic"
"addInbound" = "Add Inbound" "addInbound" = "Add Inbound"
"addTo" = "Add To" "addTo" = "Create"
"revise" = "Revise" "revise" = "Update"
"modifyInbound" = "Modify InBound" "modifyInbound" = "Modify InBound"
"deleteInbound" = "Delete Inbound" "deleteInbound" = "Delete Inbound"
"deleteInboundContent" = "Are you sure you want to delete inbound?" "deleteInboundContent" = "Are you sure you want to delete inbound?"
@@ -156,7 +156,7 @@
"first" = "First" "first" = "First"
"last" = "Last" "last" = "Last"
"prefix" = "Prefix" "prefix" = "Prefix"
"postfix" = "postfix" "postfix" = "Postfix"
"delayedStart" = "Start after first use" "delayedStart" = "Start after first use"
"expireDays" = "Expire days" "expireDays" = "Expire days"
"days" = "day(s)" "days" = "day(s)"
@@ -210,15 +210,15 @@
"xrayConfigTemplate" = "Xray Configuration Template" "xrayConfigTemplate" = "Xray Configuration Template"
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect." "xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
"xrayConfigTorrent" = "Ban bittorrent usage" "xrayConfigTorrent" = "Ban bittorrent usage"
"xrayConfigTorrentDesc" = "Change the configuration temlate to avoid using bittorrent by users, restart the panel to take effect" "xrayConfigTorrentDesc" = "Change the configuration template to avoid using bittorrent by users, restart the panel to take effect"
"xrayConfigPrivateIp" = "Ban private ip range to connect" "xrayConfigPrivateIp" = "Ban private ip range to connect"
"xrayConfigPrivateIpDesc" = "Change the configuration temlate to avoid connecting with private IP ranges, restart the panel to take effect" "xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges, restart the panel to take effect"
"xrayConfigInbounds" = "Configuration of Inbounds" "xrayConfigInbounds" = "Configuration of Inbounds"
"xrayConfigInboundsDesc" = "Change the configuration temlate to accept special clients, restart the panel to take effect" "xrayConfigInboundsDesc" = "Change the configuration template to accept special clients, restart the panel to take effect"
"xrayConfigOutbounds" = "Configuration of Outbounds" "xrayConfigOutbounds" = "Configuration of Outbounds"
"xrayConfigOutboundsDesc" = "Change the configuration temlate to define outgoing ways for this server, restart the panel to take effect" "xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server, restart the panel to take effect"
"xrayConfigRoutings" = "Configuration of Routing rules" "xrayConfigRoutings" = "Configuration of Routing rules"
"xrayConfigRoutingsDesc" = "Change the configuration temlate to define Routing rules for this server, restart the panel to take effect" "xrayConfigRoutingsDesc" = "Change the configuration template to define Routing rules for this server, restart the panel to take effect"
"telegramBotEnable" = "Enable telegram bot" "telegramBotEnable" = "Enable telegram bot"
"telegramBotEnableDesc" = "Restart the panel to take effect" "telegramBotEnableDesc" = "Restart the panel to take effect"
"telegramToken" = "Telegram Token" "telegramToken" = "Telegram Token"

View File

@@ -117,7 +117,7 @@
"network" = "شبکه" "network" = "شبکه"
"destinationPort" = "پورت مقصد" "destinationPort" = "پورت مقصد"
"targetAddress" = "آدرس مقصد" "targetAddress" = "آدرس مقصد"
"disableInsecureEncryption" = "رمزگذاری ناامن را غیرفعال کنید" "disableInsecureEncryption" = "غیرفعال سازی رمزگذاری ناامن"
"monitorDesc" = "به طور پیش فرض خالی بگذارید" "monitorDesc" = "به طور پیش فرض خالی بگذارید"
"meansNoLimit" = "یعنی بدون محدودیت" "meansNoLimit" = "یعنی بدون محدودیت"
"totalFlow" = "کل ترافیک" "totalFlow" = "کل ترافیک"

View File

@@ -39,7 +39,7 @@
"depleted" = "耗尽" "depleted" = "耗尽"
"depletingSoon" = "即将耗尽" "depletingSoon" = "即将耗尽"
"domainName" = "域名" "domainName" = "域名"
"additional" = "额外" "additional" = "额外 ID"
"monitor" = "监听" "monitor" = "监听"
"certificate" = "证书" "certificate" = "证书"
"fail" = "失败" "fail" = "失败"

View File

@@ -53,8 +53,8 @@ elif [[ "${release}" == "fedora" ]]; then
fi fi
elif [[ "${release}" == "debian" ]]; then elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 10 ]]; then
echo -e "${red} Please use Debian 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
fi fi
fi fi

View File

@@ -14,6 +14,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"time" "time"
"x-ui/config"
"x-ui/util/common" "x-ui/util/common"
"github.com/Workiva/go-datastructures/queue" "github.com/Workiva/go-datastructures/queue"
@@ -29,19 +30,19 @@ func GetBinaryName() string {
} }
func GetBinaryPath() string { func GetBinaryPath() string {
return "bin/" + GetBinaryName() return config.GetBinFolderPath() + "/" + GetBinaryName()
} }
func GetConfigPath() string { func GetConfigPath() string {
return "bin/config.json" return config.GetBinFolderPath() + "/config.json"
} }
func GetGeositePath() string { func GetGeositePath() string {
return "bin/geosite.dat" return config.GetBinFolderPath() + "/geosite.dat"
} }
func GetGeoipPath() string { func GetGeoipPath() string {
return "bin/geoip.dat" return config.GetBinFolderPath() + "/geoip.dat"
} }
func stopProcess(p *Process) { func stopProcess(p *Process) {