Merge branch 'MHSanaei:main' into main

This commit is contained in:
Mohammad Movaghari 2023-04-12 16:59:17 +03:30 committed by GitHub
commit c575425292
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1974 additions and 509 deletions

View file

@ -79,6 +79,8 @@ Set the robot-related parameters in the panel background, including:
Reference syntax: Reference syntax:
- 30 * * * * * //Notify at the 30s of each point
- 0 */10 * * * * //Notify at the first second of each 10 minutes
- @hourly // hourly notification - @hourly // hourly notification
- @daily // Daily notification (00:00 in the morning) - @daily // Daily notification (00:00 in the morning)
- @every 8h // notify every 8 hours - @every 8h // notify every 8 hours
@ -89,18 +91,40 @@ Reference syntax:
- Login notification - Login notification
- CPU threshold notification - CPU threshold notification
- Threshold for Expiration time and Traffic to report in advance - Threshold for Expiration time and Traffic to report in advance
- Support client report if client's telegram username is added to the end of `email` like 'test123@telegram_username' - Support client report menu if client's telegram username added to the user's configurations
- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously - Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously
- Menu based bot - Menu based bot
- Search client by email ( only admin ) - Search client by email ( only admin )
- Check all inbounds - Check all inbounds
- Check server status - Check server status
- Check Exhausted users - Check depleted users
- Receive backup by request and in periodic reports - Receive backup by request and in periodic reports
## API routes
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
- `/xui/API/inbounds` base for following actions:
| Method | Path | Action |
| ------------- | ------------- | ------------- |
| GET | "/list" | Get all inbounds |
| GET | "/get/:id" | Get inbound with inbound.id |
| POST | "/add" | Add inbound |
| POST | "/del/:id" | Delete Inbound |
| POST | "/update/:id" | Update Inbound |
| POST | "/clientIps/:email" | Client Ip address |
| POST | "/clearClientIps/:email" | Clear Client Ip address |
| POST | "/addClient/" | Add Client to inbound |
| POST | "/delClient/:email" | Delete Client |
| POST | "/updateClient/:index" | Update Client |
| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic |
| POST | "/resetAllTraffics" | Reset traffics of all inbounds |
| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound |
# A Special Thanks To # A Special Thanks To
- [alireza0](https://github.com/alireza0/) - [alireza0](https://github.com/alireza0/)
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/) - [FranzKafkaYu](https://github.com/FranzKafkaYu)
# Suggestion System # Suggestion System
- Ubuntu 20.04+ - Ubuntu 20.04+

View file

@ -1 +1 @@
1.1.4 1.2.2

View file

@ -44,9 +44,9 @@ type Inbound struct {
Sniffing string `json:"sniffing" form:"sniffing"` Sniffing string `json:"sniffing" form:"sniffing"`
} }
type InboundClientIps struct { type InboundClientIps struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"` Id int `json:"id" gorm:"primaryKey;autoIncrement"`
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"` ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
Ips string `json:"ips" form:"ips"` Ips string `json:"ips" form:"ips"`
} }
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
@ -80,4 +80,7 @@ type Client struct {
LimitIP int `json:"limitIp"` LimitIP int `json:"limitIp"`
TotalGB int64 `json:"totalGB" form:"totalGB"` TotalGB int64 `json:"totalGB" form:"totalGB"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
Enable bool `json:"enable" form:"enable"`
TgID string `json:"tgId" form:"tgId"`
SubID string `json:"subId" form:"subId"`
} }

36
go.mod
View file

@ -8,6 +8,7 @@ require (
github.com/gin-gonic/gin v1.9.0 github.com/gin-gonic/gin v1.9.0
github.com/go-cmd/cmd v1.4.1 github.com/go-cmd/cmd v1.4.1
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/goccy/go-json v0.10.2
github.com/nicksnyder/go-i18n/v2 v2.2.1 github.com/nicksnyder/go-i18n/v2 v2.2.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.0.7 github.com/pelletier/go-toml/v2 v2.0.7
@ -15,22 +16,21 @@ require (
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.0
go.uber.org/atomic v1.10.0 go.uber.org/atomic v1.10.0
golang.org/x/text v0.8.0 golang.org/x/text v0.9.0
google.golang.org/grpc v1.54.0 google.golang.org/grpc v1.54.0
gorm.io/driver/sqlite v1.4.4 gorm.io/driver/sqlite v1.5.0
gorm.io/gorm v1.24.6 gorm.io/gorm v1.25.0
) )
require ( require (
github.com/BurntSushi/toml v1.2.1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect
github.com/bytedance/sonic v1.8.2 // 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/goccy/go-json v0.10.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
@ -39,25 +39,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-20230110061619-bbe2e5e100de // 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-20221212215047-62379fc7944b // 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.10 // 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.2.0 // 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.1 // 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
) )

80
go.sum
View file

@ -9,8 +9,8 @@ github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
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.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY= github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 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=
@ -41,14 +41,14 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
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.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@ -73,7 +73,6 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -84,18 +83,16 @@ github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw
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=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
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.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 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/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-20230110061619-bbe2e5e100de h1:V53FWzU6KAZVi1tPp5UIsMoUWJ2/PNwYIDXnu7QuBCE= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= 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.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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=
@ -116,8 +113,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
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=
@ -132,15 +129,15 @@ github.com/refraction-networking/utls v1.2.3-0.20230308205431-4f1df6c200db h1:UL
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/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08= github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08=
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q= github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/shirou/gopsutil/v3 v3.23.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/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=
@ -148,7 +145,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -165,8 +161,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
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.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/xtls/reality v0.0.0-20230309125256-0d0713b108c8 h1:LLtLxEe3S0Ko+ckqt4t29RLskpNdOZfgjZCC2/Byr50= github.com/xtls/reality v0.0.0-20230309125256-0d0713b108c8 h1:LLtLxEe3S0Ko+ckqt4t29RLskpNdOZfgjZCC2/Byr50=
github.com/xtls/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k= github.com/xtls/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k=
@ -179,14 +175,14 @@ go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfG
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -196,8 +192,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
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=
@ -213,10 +209,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.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,8 +220,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -236,16 +232,16 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
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.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.29.1/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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -253,11 +249,11 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc= gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s= gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4= gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View file

@ -66,33 +66,17 @@ 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
fi fi
# This function installs the base packages required for most scripts
install_base() { install_base() {
# Store the package names in a variable for easy modification case "${release}" in
local packages="wget curl tar" centos|fedora)
yum install -y -q wget curl tar
# Check for the package managers and install the packages if they are not already installed ;;
if ! command -v wget >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/null 2>&1; then *)
if command -v apt >/dev/null 2>&1; then apt install -y -q wget curl tar
apt-get update && apt-get install -y $packages ;;
elif command -v dnf >/dev/null 2>&1; then esac
dnf install -y $packages
elif command -v yum >/dev/null 2>&1; then
yum install -y $packages
else
echo "ERROR: No package managers found. Please install wget, curl, and tar manually."
return 1
fi
# Print a confirmation message after the installation is complete
echo "The following packages have been successfully installed: $packages"
else
# Print a message confirming that the packages are already installed
echo "The following packages are already installed: $packages"
fi
} }
#This function will be called when user installed x-ui out of sercurity #This function will be called when user installed x-ui out of sercurity
config_after_install() { config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}" echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View file

@ -156,6 +156,12 @@
padding:16px; padding:16px;
} }
.ant-table-expand-icon-th,
.ant-table-row-expand-icon-cell {
width: 30px;
min-width: 30px;
}
.ant-menu-dark, .ant-menu-dark,
.ant-menu-dark .ant-menu-sub, .ant-menu-dark .ant-menu-sub,
.ant-layout-header, .ant-layout-header,
@ -174,6 +180,7 @@
.ant-card-dark:hover { .ant-card-dark:hover {
border-color: #e8e8e8; border-color: #e8e8e8;
box-shadow: 0 2px 8px rgba(255,255,255,.15);
} }
.ant-card-dark .ant-table-thead th { .ant-card-dark .ant-table-thead th {
@ -216,20 +223,25 @@
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td, .ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled), .ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
.ant-card-dark .ant-calendar-date:hover { .ant-card-dark .ant-calendar-date:hover,
.ant-card-dark .ant-select-dropdown-menu-item-active,
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
background-color: #004488; background-color: #004488;
} }
.ant-card-dark tbody .ant-table-expanded-row { .ant-card-dark tbody .ant-table-expanded-row,
.ant-card-dark .ant-calendar-time-picker-inner {
color: hsla(0,0%,100%,.65); color: hsla(0,0%,100%,.65);
background-color: #1a212a; background-color: #1a212a;
} }
.ant-card-dark .ant-input, .ant-card-dark .ant-input,
.ant-card-dark .ant-input-number, .ant-card-dark .ant-input-number,
.ant-card-dark .ant-input-number-handler-wrap,
.ant-card-dark .ant-calendar-input, .ant-card-dark .ant-calendar-input,
.ant-card-dark .ant-select-dropdown-menu-item-selected, .ant-card-dark .ant-select-dropdown-menu-item-selected,
.ant-card-dark .ant-select-selection { .ant-card-dark .ant-select-selection,
.ant-card-dark .ant-calendar-picker-clear {
color: hsla(0,0%,100%,.65); color: hsla(0,0%,100%,.65);
background-color: #2e3b52; background-color: #2e3b52;
} }
@ -239,6 +251,12 @@
background-color: #161b22; background-color: #161b22;
} }
.ant-dropdown-menu-dark,
.ant-card-dark .ant-modal-content {
border: 1px solid rgba(255, 255, 255, 0.65);
box-shadow: 0 2px 8px rgba(255,255,255,.15);
}
.ant-card-dark .ant-modal-content, .ant-card-dark .ant-modal-content,
.ant-card-dark .ant-modal-body, .ant-card-dark .ant-modal-body,
.ant-card-dark .ant-modal-header, .ant-card-dark .ant-modal-header,
@ -280,6 +298,12 @@
border: 1px solid hsla(0,0%,100%,.30); border: 1px solid hsla(0,0%,100%,.30);
} }
.ant-card-dark .ant-tag {
color: hsla(0,0%,100%,.65);
background: rgba(255,255,255,.04);
border-color: #434343;
}
.ant-card-dark .ant-tag-blue { .ant-card-dark .ant-tag-blue {
color: #3c9ae8; color: #3c9ae8;
background: #111d2c; background: #111d2c;
@ -334,6 +358,29 @@
color: hsla(0,0%,100%,.65); color: hsla(0,0%,100%,.65);
background-color: #073763; background-color: #073763;
border-color: #1890ff; border-color: #1890ff;
text-shadow: 0 -1px 0 rgba(0,0,0,.12); text-shadow: 0 -1px 0 rgba(255,255,255,.12);
box-shadow: 0 2px 0 rgba(0,0,0,.045); box-shadow: 0 2px 0 rgba(255,255,255,.045);
}
.ant-card-dark .ant-btn-primary:hover {
background-color: #40a9ff;
border-color: #40a9ff;
}
.ant-dark .ant-popover-content {
border: 1px solid #e8e8e8;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(255,255,255,.15);
}
.ant-dark .ant-popover-inner {
background: #222a37;
}
.ant-dark .ant-popover-title,
.ant-dark .ant-popover-inner-content {
color: hsla(0,0%,100%,.65);
}
.ant-dark .ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow {
border-color: transparent #2e3b52 #2e3b52 transparent;
} }

View file

@ -171,13 +171,13 @@ class AllSetting {
this.webCertFile = ""; this.webCertFile = "";
this.webKeyFile = ""; this.webKeyFile = "";
this.webBasePath = "/"; this.webBasePath = "/";
this.expireDiff = "";
this.trafficDiff = "";
this.tgBotEnable = false; this.tgBotEnable = false;
this.tgBotToken = ""; this.tgBotToken = "";
this.tgBotChatId = ""; this.tgBotChatId = "";
this.tgRunTime = "@daily"; this.tgRunTime = "@daily";
this.tgBotBackup = false; this.tgBotBackup = false;
this.tgExpireDiff = "";
this.tgTrafficDiff = "";
this.tgCpu = ""; this.tgCpu = "";
this.xrayTemplateConfig = ""; this.xrayTemplateConfig = "";

View file

@ -91,7 +91,11 @@ const UTLS_FINGERPRINT = {
UTLS_RANDOMIZED: "randomized", UTLS_RANDOMIZED: "randomized",
}; };
const bytesToHex = e => Array.from(e).map(e => e.toString(16).padStart(2, 0)).join('');
const hexToBytes = e => new Uint8Array(e.match(/[0-9a-f]{2}/gi).map(e => parseInt(e, 16)));
const ALPN_OPTION = { const ALPN_OPTION = {
H3: "h3",
H2: "h2", H2: "h2",
HTTP1: "http/1.1", HTTP1: "http/1.1",
}; };
@ -105,7 +109,6 @@ Object.freeze(XTLS_FLOW_CONTROL);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(TLS_VERSION_OPTION); Object.freeze(TLS_VERSION_OPTION);
Object.freeze(TLS_CIPHER_OPTION); Object.freeze(TLS_CIPHER_OPTION);
Object.freeze(UTLS_FINGERPRINT);
Object.freeze(ALPN_OPTION); Object.freeze(ALPN_OPTION);
class XrayCommonClass { class XrayCommonClass {
@ -166,27 +169,25 @@ class XrayCommonClass {
} }
class TcpStreamSettings extends XrayCommonClass { class TcpStreamSettings extends XrayCommonClass {
constructor( constructor(acceptProxyProtocol=false,
type = 'none', type='none',
acceptProxyProtocol = false, request=new TcpStreamSettings.TcpRequest(),
request = new TcpStreamSettings.TcpRequest(), response=new TcpStreamSettings.TcpResponse(),
response = new TcpStreamSettings.TcpResponse(), ) {
) {
super(); super();
this.acceptProxyProtocol = acceptProxyProtocol;
this.type = type; this.type = type;
this.request = request; this.request = request;
this.response = response; this.response = response;
this.acceptProxyProtocol = acceptProxyProtocol;
} }
static fromJson(json = {}) { static fromJson(json={}) {
let header = json.header; let header = json.header;
if (!header) { if (!header) {
header = {}; header = {};
} }
return new TcpStreamSettings( return new TcpStreamSettings(json.acceptProxyProtocol,
header.type, header.type,
json.acceptProxyProtocol,
TcpStreamSettings.TcpRequest.fromJson(header.request), TcpStreamSettings.TcpRequest.fromJson(header.request),
TcpStreamSettings.TcpResponse.fromJson(header.response), TcpStreamSettings.TcpResponse.fromJson(header.response),
); );
@ -194,21 +195,21 @@ class TcpStreamSettings extends XrayCommonClass {
toJson() { toJson() {
return { return {
acceptProxyProtocol: this.acceptProxyProtocol,
header: { header: {
type: this.type, type: this.type,
request: this.type === 'http' ? this.request.toJson() : undefined, request: this.type === 'http' ? this.request.toJson() : undefined,
response: this.type === 'http' ? this.response.toJson() : undefined, response: this.type === 'http' ? this.response.toJson() : undefined,
}, },
acceptProxyProtocol: this.acceptProxyProtocol,
}; };
} }
} }
TcpStreamSettings.TcpRequest = class extends XrayCommonClass { TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
constructor(version = '1.1', constructor(version='1.1',
method = 'GET', method='GET',
path = ['/'], path=['/'],
headers = [], headers=[],
) { ) {
super(); super();
this.version = version; this.version = version;
@ -242,7 +243,7 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
this.headers.splice(index, 1); this.headers.splice(index, 1);
} }
static fromJson(json = {}) { static fromJson(json={}) {
return new TcpStreamSettings.TcpRequest( return new TcpStreamSettings.TcpRequest(
json.version, json.version,
json.method, json.method,
@ -261,10 +262,10 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
}; };
TcpStreamSettings.TcpResponse = class extends XrayCommonClass { TcpStreamSettings.TcpResponse = class extends XrayCommonClass {
constructor(version = '1.1', constructor(version='1.1',
status = '200', status='200',
reason = 'OK', reason='OK',
headers = [], headers=[],
) { ) {
super(); super();
this.version = version; this.version = version;
@ -281,7 +282,7 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass {
this.headers.splice(index, 1); this.headers.splice(index, 1);
} }
static fromJson(json = {}) { static fromJson(json={}) {
return new TcpStreamSettings.TcpResponse( return new TcpStreamSettings.TcpResponse(
json.version, json.version,
json.status, json.status,
@ -474,9 +475,13 @@ class GrpcStreamSettings extends XrayCommonClass {
} }
class TlsStreamSettings extends XrayCommonClass { class TlsStreamSettings extends XrayCommonClass {
constructor(serverName = '', minVersion = TLS_VERSION_OPTION.TLS10, maxVersion = TLS_VERSION_OPTION.TLS12, constructor(serverName='',
cipherSuites = '', minVersion = TLS_VERSION_OPTION.TLS12,
certificates = [new TlsStreamSettings.Cert()], alpn=[''] ,settings=[new TlsStreamSettings.Settings()]) { maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '',
certificates=[new TlsStreamSettings.Cert()],
alpn=[],
settings=[new TlsStreamSettings.Settings()]) {
super(); super();
this.server = serverName; this.server = serverName;
this.minVersion = minVersion; this.minVersion = minVersion;
@ -484,7 +489,7 @@ class TlsStreamSettings extends XrayCommonClass {
this.cipherSuites = cipherSuites; this.cipherSuites = cipherSuites;
this.certs = certificates; this.certs = certificates;
this.alpn = alpn; this.alpn = alpn;
this.settings = settings; this.settings = settings;
} }
addCert(cert) { addCert(cert) {
@ -497,15 +502,15 @@ class TlsStreamSettings extends XrayCommonClass {
static fromJson(json={}) { static fromJson(json={}) {
let certs; let certs;
let settings; let settings;
if (!ObjectUtil.isEmpty(json.certificates)) { if (!ObjectUtil.isEmpty(json.certificates)) {
certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert)); certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert));
} }
if (!ObjectUtil.isEmpty(json.settings)) { if (!ObjectUtil.isEmpty(json.settings)) {
let values = json.settings[0]; let values = json.settings[0];
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)]; settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
} }
return new TlsStreamSettings( return new TlsStreamSettings(
json.serverName, json.serverName,
json.minVersion, json.minVersion,
@ -513,7 +518,7 @@ class TlsStreamSettings extends XrayCommonClass {
json.cipherSuites, json.cipherSuites,
certs, certs,
json.alpn, json.alpn,
settings, settings,
); );
} }
@ -526,7 +531,6 @@ class TlsStreamSettings extends XrayCommonClass {
certificates: TlsStreamSettings.toJsonArray(this.certs), certificates: TlsStreamSettings.toJsonArray(this.certs),
alpn: this.alpn, alpn: this.alpn,
settings: TlsStreamSettings.toJsonArray(this.settings), settings: TlsStreamSettings.toJsonArray(this.settings),
}; };
} }
} }
@ -573,44 +577,105 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
}; };
TlsStreamSettings.Settings = class extends XrayCommonClass { TlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(allowInsecure = false, fingerprint = '', serverName = '') { constructor(allowInsecure = false, fingerprint = '', serverName = '') {
super(); super();
this.allowInsecure = allowInsecure; this.allowInsecure = allowInsecure;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;
this.serverName = serverName; this.serverName = serverName;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
return new TlsStreamSettings.Settings( return new TlsStreamSettings.Settings(
json.allowInsecure, json.allowInsecure,
json.fingerprint, json.fingerprint,
json.servername, json.servername,
); );
} }
toJson() { toJson() {
return { return {
allowInsecure: this.allowInsecure, allowInsecure: this.allowInsecure,
fingerprint: this.fingerprint, fingerprint: this.fingerprint,
serverName: this.serverName, serverName: this.serverName,
}; };
} }
}; };
class RealityStreamSettings extends XrayCommonClass {
constructor(
show = false,xver = 0,
fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX,
dest = 'yahoo.com:443',
serverNames = 'yahoo.com,www.yahoo.com',
privateKey = RandomUtil.randomX25519PrivateKey(),
publicKey = '',
minClient = '',
maxClient = '',
maxTimediff = 0,
shortIds = RandomUtil.randowShortId()
)
{
super();
this.show = show;
this.xver = xver;
this.fingerprint = fingerprint;
this.dest = dest;
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
this.privateKey = privateKey;
this.publicKey = RandomUtil.randomX25519PublicKey(this.privateKey);
this.minClient = minClient;
this.maxClient = maxClient;
this.maxTimediff = maxTimediff;
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
}
static fromJson(json = {}) {
return new RealityStreamSettings(
json.show,
json.xver,
json.fingerprint,
json.dest,
json.serverNames,
json.privateKey,
json.publicKey,
json.minClient,
json.maxClient,
json.maxTimediff,
json.shortIds
);
}
toJson() {
return {
show: this.show,
xver: this.xver,
fingerprint: this.fingerprint,
dest: this.dest,
serverNames: this.serverNames.split(/,||\s+/),
privateKey: this.privateKey,
publicKey: this.publicKey,
minClient: this.minClient,
maxClient: this.maxClient,
maxTimediff: this.maxTimediff,
shortIds: this.shortIds.split(/,||\s+/)
};
}
}
class StreamSettings extends XrayCommonClass { class StreamSettings extends XrayCommonClass {
constructor(network='tcp', constructor(network='tcp',
security='none', security='none',
tlsSettings=new TlsStreamSettings(), tlsSettings=new TlsStreamSettings(),
tcpSettings=new TcpStreamSettings(), realitySettings = new RealityStreamSettings(),
kcpSettings=new KcpStreamSettings(), tcpSettings=new TcpStreamSettings(),
wsSettings=new WsStreamSettings(), kcpSettings=new KcpStreamSettings(),
httpSettings=new HttpStreamSettings(), wsSettings=new WsStreamSettings(),
quicSettings=new QuicStreamSettings(), httpSettings=new HttpStreamSettings(),
grpcSettings=new GrpcStreamSettings(), quicSettings=new QuicStreamSettings(),
) { grpcSettings=new GrpcStreamSettings(),
) {
super(); super();
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;
@ -643,17 +708,34 @@ class StreamSettings extends XrayCommonClass {
} }
} }
static fromJson(json={}) { //for Reality
let tls; get isReality() {
return this.security === "reality";
}
set isReality(isReality) {
if (isReality) {
this.security = "reality";
} else {
this.security = "none";
}
}
static fromJson(json = {}) {
let tls, reality;
if (json.security === "xtls") { if (json.security === "xtls") {
tls = TlsStreamSettings.fromJson(json.XTLSSettings); tls = TlsStreamSettings.fromJson(json.XTLSSettings);
} else { } else if (json.security === "tls") {
tls = TlsStreamSettings.fromJson(json.tlsSettings); tls = TlsStreamSettings.fromJson(json.tlsSettings);
} }
if (json.security === "reality") {
reality = RealityStreamSettings.fromJson(json.realitySettings)
}
return new StreamSettings( return new StreamSettings(
json.network, json.network,
json.security, json.security,
tls, tls,
reality,
TcpStreamSettings.fromJson(json.tcpSettings), TcpStreamSettings.fromJson(json.tcpSettings),
KcpStreamSettings.fromJson(json.kcpSettings), KcpStreamSettings.fromJson(json.kcpSettings),
WsStreamSettings.fromJson(json.wsSettings), WsStreamSettings.fromJson(json.wsSettings),
@ -671,6 +753,7 @@ class StreamSettings extends XrayCommonClass {
tlsSettings: this.isTls ? this.tls.toJson() : undefined, tlsSettings: this.isTls ? this.tls.toJson() : undefined,
XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined, XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined,
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined, tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
realitySettings: this.isReality ? this.reality.toJson() : undefined,
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined, kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
wsSettings: network === 'ws' ? this.ws.toJson() : undefined, wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
httpSettings: network === 'http' ? this.http.toJson() : undefined, httpSettings: network === 'http' ? this.http.toJson() : undefined,
@ -733,15 +816,18 @@ class Inbound extends XrayCommonClass {
this._protocol = protocol; this._protocol = protocol;
this.settings = Inbound.Settings.getSettings(protocol); this.settings = Inbound.Settings.getSettings(protocol);
if (protocol === Protocols.TROJAN) { if (protocol === Protocols.TROJAN) {
this.tls = false; this.tls = true;
} }
} }
get tls() { get tls() {
return this.stream.security === 'tls'; return this.stream.security === 'tls';
} }
set tls(isTls) { set tls(isTls) {
if (isTls) { if (isTls) {
this.xtls = false;
this.reality = false;
this.stream.security = 'tls'; this.stream.security = 'tls';
} else { } else {
this.stream.security = 'none'; this.stream.security = 'none';
@ -754,12 +840,32 @@ class Inbound extends XrayCommonClass {
set XTLS(isXTLS) { set XTLS(isXTLS) {
if (isXTLS) { if (isXTLS) {
this.xtls = false;
this.reality = false;
this.stream.security = 'xtls'; this.stream.security = 'xtls';
} else { } else {
this.stream.security = 'none'; this.stream.security = 'none';
} }
} }
//for Reality
get reality() {
if (this.stream.security === "reality") {
return this.network === "tcp" || this.network === "grpc" || this.network === "http";
}
return false;
}
set reality(isReality) {
if (isReality) {
this.tls = false;
this.xtls = false;
this.stream.security = "reality";
} else {
this.stream.security = "none";
}
}
get network() { get network() {
return this.stream.network; return this.stream.network;
} }
@ -918,16 +1024,16 @@ class Inbound extends XrayCommonClass {
isExpiry(index) { isExpiry(index) {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VMESS: case Protocols.VMESS:
if(this.settings.vmesses[index]._expiryTime != null) if(this.settings.vmesses[index].expiryTime > 0)
return this.settings.vmesses[index]._expiryTime < new Date().getTime(); return this.settings.vmesses[index].expiryTime < new Date().getTime();
return false return false
case Protocols.VLESS: case Protocols.VLESS:
if(this.settings.vlesses[index]._expiryTime != null) if(this.settings.vlesses[index].expiryTime > 0)
return this.settings.vlesses[index]._expiryTime < new Date().getTime(); return this.settings.vlesses[index].expiryTime < new Date().getTime();
return false return false
case Protocols.TROJAN: case Protocols.TROJAN:
if(this.settings.trojans[index]._expiryTime != null) if(this.settings.trojans[index].expiryTime > 0)
return this.settings.trojans[index]._expiryTime < new Date().getTime(); return this.settings.trojans[index].expiryTime < new Date().getTime();
return false return false
default: default:
return false; return false;
@ -956,9 +1062,20 @@ class Inbound extends XrayCommonClass {
} }
} }
canEnableReality() {
switch (this.protocol) {
case Protocols.VLESS:
case Protocols.TROJAN:
break;
default:
return false;
}
return this.network === "tcp" || this.network === "grpc" || this.network === "http";
}
//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 === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VLESS: case Protocols.VLESS:
return true; return true;
@ -973,7 +1090,6 @@ class Inbound extends XrayCommonClass {
return this.canEnableTls(); return this.canEnableTls();
} }
canEnableXTLS() { canEnableXTLS() {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VLESS: case Protocols.VLESS:
@ -989,7 +1105,8 @@ class Inbound extends XrayCommonClass {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VMESS: case Protocols.VMESS:
case Protocols.VLESS: case Protocols.VLESS:
case Protocols.TROJAN: case Protocols.TROJAN:
case Protocols.SHADOWSOCKS:
return true; return true;
default: default:
return false; return false;
@ -1078,7 +1195,7 @@ 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[0]['serverName'],
fp: this.stream.tls.settings[0]['fingerprint'], fp: this.stream.tls.settings[0]['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[0].allowInsecure,
@ -1148,7 +1265,7 @@ class Inbound extends XrayCommonClass {
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[0]['serverName'] !== ''){
params.set("sni", this.stream.tls.settings[0]['serverName']); params.set("sni", this.stream.tls.settings[0]['serverName']);
} }
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) { if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
@ -1156,17 +1273,37 @@ class Inbound extends XrayCommonClass {
} }
} }
if (this.XTLS) { if (this.XTLS) {
params.set("security", "xtls"); params.set("security", "xtls");
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){ if(this.stream.tls.settings[0].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;
} }
params.set("flow", this.settings.vlesses[clientIndex].flow); params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
if (this.reality) {
params.set("security", "reality");
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("sni", this.stream.reality.serverNames.split(/,||\s+/)[0]);
}
if (this.stream.reality.publicKey != "") {
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey));
params.set("pbk", this.stream.reality.publicKey);
}
if (this.stream.network === 'tcp') {
params.set("flow", this.settings.vlesses[clientIndex].flow);
}
if (this.stream.reality.shortIds != "") {
params.set("sid", this.stream.reality.shortIds);
}
if (this.stream.reality.fingerprint != "") {
params.set("fp", this.stream.reality.fingerprint);
}
}
const link = `vless://${uuid}@${address}:${port}`; const link = `vless://${uuid}@${address}:${port}`;
const url = new URL(link); const url = new URL(link);
@ -1177,13 +1314,13 @@ class Inbound extends XrayCommonClass {
return url.toString(); return url.toString();
} }
genSSLink(address = '', remark = '') { genSSLink(address='', remark='') {
let settings = this.settings; let settings = this.settings;
const server = this.stream.tls.server; const server = this.stream.tls.server;
if (!ObjectUtil.isEmpty(server)) { if (!ObjectUtil.isEmpty(server)) {
address = server; address = server;
} }
return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`; return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`;
} }
genTrojanLink(address = '', remark = '', clientIndex = 0) { genTrojanLink(address = '', remark = '', clientIndex = 0) {
@ -1191,7 +1328,7 @@ class Inbound extends XrayCommonClass {
const port = this.port; const port = this.port;
const type = this.stream.network; const type = this.stream.network;
const params = new Map(); const params = new Map();
params.set("type", this.stream.network); params.set("type", this.stream.network);
switch (type) { switch (type) {
case "tcp": case "tcp":
const tcp = this.stream.tcp; const tcp = this.stream.tcp;
@ -1246,9 +1383,29 @@ class Inbound extends XrayCommonClass {
} }
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[0]['serverName'] !== ''){
params.set("sni", this.stream.tls.settings[0]['serverName']); params.set("sni", this.stream.tls.settings[0]['serverName']);
}
}
if (this.reality) {
params.set("security", "reality");
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("sni", this.stream.reality.serverNames.split(/,||\s+/)[0]);
}
if (this.stream.reality.publicKey != "") {
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey));
params.set("pbk", this.stream.reality.publicKey);
}
if (this.stream.network === 'tcp') {
params.set("flow", this.settings.trojans[clientIndex].flow);
}
if (this.stream.reality.shortIds != "") {
params.set("sid", this.stream.reality.shortIds);
}
if (this.stream.reality.fingerprint != "") {
params.set("fp", this.stream.reality.fingerprint);
} }
} }
@ -1259,10 +1416,10 @@ class Inbound extends XrayCommonClass {
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;
} }
params.set("flow", this.settings.trojans[clientIndex].flow); params.set("flow", this.settings.trojans[clientIndex].flow);
} }
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`; const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
const url = new URL(link); const url = new URL(link);
@ -1294,8 +1451,9 @@ class Inbound extends XrayCommonClass {
default: return ''; default: return '';
} }
} }
genInboundLinks(address = '', remark = '') { genInboundLinks(address = '', remark = '') {
let link = ''; let link = '';
switch (this.protocol) { switch (this.protocol) {
case Protocols.VMESS: case Protocols.VMESS:
case Protocols.VLESS: case Protocols.VLESS:
@ -1308,7 +1466,7 @@ class Inbound extends XrayCommonClass {
return (this.genSSLink(address, remark) + '\r\n'); return (this.genSSLink(address, remark) + '\r\n');
default: return ''; default: return '';
} }
} }
static fromJson(json={}) { static fromJson(json={}) {
return new Inbound( return new Inbound(
@ -1423,7 +1581,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
} }
}; };
Inbound.VmessSettings.Vmess = class extends XrayCommonClass { Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') { constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
super(); super();
this.id = id; this.id = id;
this.alterId = alterId; this.alterId = alterId;
@ -1431,6 +1589,9 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
this.limitIp = limitIp; this.limitIp = limitIp;
this.totalGB = totalGB; this.totalGB = totalGB;
this.expiryTime = expiryTime; this.expiryTime = expiryTime;
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
} }
static fromJson(json={}) { static fromJson(json={}) {
@ -1441,13 +1602,18 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
json.limitIp, json.limitIp,
json.totalGB, json.totalGB,
json.expiryTime, json.expiryTime,
json.enable,
json.tgId,
json.subId,
); );
} }
get _expiryTime() { get _expiryTime() {
if (this.expiryTime === 0 || this.expiryTime === "") { if (this.expiryTime === 0 || this.expiryTime === "") {
return null; return null;
} }
if (this.expiryTime < 0){
return this.expiryTime / -86400000;
}
return moment(this.expiryTime); return moment(this.expiryTime);
} }
@ -1475,7 +1641,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
fallbacks=[],) { fallbacks=[],) {
super(protocol); super(protocol);
this.vlesses = vlesses; this.vlesses = vlesses;
this.decryption = 'none'; this.decryption = 'none'; // Using decryption is not implemented here
this.fallbacks = fallbacks; this.fallbacks = fallbacks;
} }
@ -1487,6 +1653,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
this.fallbacks.splice(index, 1); this.fallbacks.splice(index, 1);
} }
// decryption should be set to static value
static fromJson(json={}) { static fromJson(json={}) {
return new Inbound.VLESSSettings( return new Inbound.VLESSSettings(
Protocols.VLESS, Protocols.VLESS,
@ -1499,15 +1666,14 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
toJson() { toJson() {
return { return {
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses), clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
decryption: this.decryption, decryption: 'none',
fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks), fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
}; };
} }
}; };
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') {
super(); super();
this.id = id; this.id = id;
this.flow = flow; this.flow = flow;
@ -1515,7 +1681,9 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
this.limitIp = limitIp; this.limitIp = limitIp;
this.totalGB = totalGB; this.totalGB = totalGB;
this.expiryTime = expiryTime; this.expiryTime = expiryTime;
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
} }
static fromJson(json={}) { static fromJson(json={}) {
@ -1526,14 +1694,19 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
json.limitIp, json.limitIp,
json.totalGB, json.totalGB,
json.expiryTime, json.expiryTime,
json.enable,
json.tgId,
json.subId,
); );
} }
get _expiryTime() { get _expiryTime() {
if (this.expiryTime === 0 || this.expiryTime === "") { if (this.expiryTime === 0 || this.expiryTime === "") {
return null; return null;
} }
if (this.expiryTime < 0){
return this.expiryTime / -86400000;
}
return moment(this.expiryTime); return moment(this.expiryTime);
} }
@ -1593,8 +1766,8 @@ Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
Inbound.TrojanSettings = class extends Inbound.Settings { Inbound.TrojanSettings = class extends Inbound.Settings {
constructor(protocol, constructor(protocol,
trojans=[new Inbound.TrojanSettings.Trojan()], trojans=[new Inbound.TrojanSettings.Trojan()],
fallbacks=[],) { fallbacks=[],) {
super(protocol); super(protocol);
this.trojans = trojans; this.trojans = trojans;
this.fallbacks = fallbacks; this.fallbacks = fallbacks;
@ -1623,7 +1796,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
} }
}; };
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') { constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
super(); super();
this.password = password; this.password = password;
this.flow = flow; this.flow = flow;
@ -1631,6 +1804,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
this.limitIp = limitIp; this.limitIp = limitIp;
this.totalGB = totalGB; this.totalGB = totalGB;
this.expiryTime = expiryTime; this.expiryTime = expiryTime;
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
} }
toJson() { toJson() {
@ -1641,10 +1817,13 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
limitIp: this.limitIp, limitIp: this.limitIp,
totalGB: this.totalGB, totalGB: this.totalGB,
expiryTime: this.expiryTime, expiryTime: this.expiryTime,
enable: this.enable,
tgId: this.tgId,
subId: this.subId,
}; };
} }
static fromJson(json={}) { static fromJson(json = {}) {
return new Inbound.TrojanSettings.Trojan( return new Inbound.TrojanSettings.Trojan(
json.password, json.password,
json.flow, json.flow,
@ -1652,7 +1831,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
json.limitIp, json.limitIp,
json.totalGB, json.totalGB,
json.expiryTime, json.expiryTime,
json.enable,
json.tgId,
json.subId,
); );
} }
@ -1660,6 +1841,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
if (this.expiryTime === 0 || this.expiryTime === "") { if (this.expiryTime === 0 || this.expiryTime === "") {
return null; return null;
} }
if (this.expiryTime < 0){
return this.expiryTime / -86400000;
}
return moment(this.expiryTime); return moment(this.expiryTime);
} }
@ -1721,9 +1905,9 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
Inbound.ShadowsocksSettings = class extends Inbound.Settings { Inbound.ShadowsocksSettings = class extends Inbound.Settings {
constructor(protocol, constructor(protocol,
method = SSMethods.BLAKE3_AES_256_GCM, method=SSMethods.BLAKE3_AES_256_GCM,
password = RandomUtil.randomSeq(44), password=RandomUtil.randomSeq(44),
network = 'tcp,udp' network='tcp,udp'
) { ) {
super(protocol); super(protocol);
this.method = method; this.method = method;
@ -1731,7 +1915,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
this.network = network; this.network = network;
} }
static fromJson(json = {}) { static fromJson(json={}) {
return new Inbound.ShadowsocksSettings( return new Inbound.ShadowsocksSettings(
Protocols.SHADOWSOCKS, Protocols.SHADOWSOCKS,
json.method, json.method,
@ -1755,7 +1939,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
this.address = address; this.address = address;
this.port = port; this.port = port;
this.network = network; this.network = network;
this.followRedirect = followRedirect; this.followRedirect = followRedirect;
} }
static fromJson(json={}) { static fromJson(json={}) {
@ -1764,7 +1948,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
json.address, json.address,
json.port, json.port,
json.network, json.network,
json.followRedirect, json.followRedirect,
); );
} }
@ -1773,7 +1957,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
address: this.address, address: this.address,
port: this.port, port: this.port,
network: this.network, network: this.network,
followRedirect: this.followRedirect, followRedirect: this.followRedirect,
}; };
} }
}; };

View file

@ -89,6 +89,31 @@ const seq = [
'U', 'V', 'W', 'X', 'Y', 'Z' 'U', 'V', 'W', 'X', 'Y', 'Z'
]; ];
const shortIdSeq = [
'a', 'b', 'c', 'd', 'e', 'f',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
];
const x25519Map = new Map(
[
['EH2FWe-Ij_FFAa2u9__-aiErLvVIneP601GOCdlyPWw', "goY3OtfaA4UYbiz7Hn0NysI5QJrK0VT_Chg6RLgUPQU"],
['cKI_6DoMSP1IepeWWXrG3G9nkehl94KYBhagU50g2U0', "VigpKFbSLnHLzBWobZaS1IBmw--giJ51w92y723ajnU"],
['qM2SNyK3NyHB6deWpEP3ITyCGKQFRTna_mlKP0w1QH0', "HYyIGuyNFslmcnNT7mrDdmuXwn4cm7smE_FZbYguKHQ"],
['qCWg5GMEDFd3n1nxDswlIpOHoPUXMLuMOIiLUVzubkI', "rJFC3dUjJxMnVZiUGzmf_LFsJUwFWY-CU5RQgFOHCWM"],
['4NOBxDrEsOhNI3Y3EnVIy_TN-uyBoAjQw6QM0YsOi0s', "CbcY9qc4YuMDJDyyL0OITlU824TBg1O84ClPy27e2RM"],
['eBvFb0M4HpSOwWjtXV8zliiEs_hg56zX4a2LpuuqpEI', "CjulQ2qVIky7ImIfysgQhNX7s_drGLheCGSkVHcLZhc"],
['yEpOzQV04NNcycWVeWtRNTzv5TS-ynTuKRacZCH-6U8', "O9RSr5gSdok2K_tobQnf_scyKVqnCx6C4Jrl7_rCZEQ"],
['CNt6TAUVCwqM6xIBHyni0K3Zqbn2htKQLvLb6XDgh0s', "d9cGLVBrDFS02L2OvkqyqwFZ1Ux3AHs28ehl4Rwiyl0"],
['EInKw-6Wr0rAHXlxxDuZU5mByIzcD3Z-_iWPzXlUL1k', "LlYD2nNVAvyjNvjZGZh4R8PkMIwkc6EycPTvR2LE0nQ"],
['GKIKo7rcXVyle-EUHtGIDtYnDsI6osQmOUl3DTJRAGc', "VcqHivYGGoBkcxOI6cSSjQmneltstkb2OhvO53dyhEM"],
['-FVDzv68IC17fJVlNDlhrrgX44WeBfbhwjWpCQVXGHE', "PGG2EYOvsFt2lAQTD7lqHeRxz2KxvllEDKcUrtizPBU"],
['0H3OJEYEu6XW7woqy7cKh2vzg6YHkbF_xSDTHKyrsn4', "mzevpYbS8kXengBY5p7tt56QE4tS3lwlwRemmkcQeyc"],
['8F8XywN6ci44ES6em2Z0fYYxyptB9uaXY9Hc1WSSPE4', "qCZUdWQZ2H33vWXnOkG8NpxBeq3qn5QWXlfCOWBNkkc"],
['IN0dqfkC10dj-ifRHrg2PmmOrzYs697ajGMwcLbu-1g', "2UW_EO3r7uczPGUUlpJBnMDpDmWUHE2yDzCmXS4sckE"],
['uIcmks5rAhvBe4dRaJOdeSqgxLGGMZhsGk4J4PEKL2s', "F9WJV_74IZp0Ide4hWjiJXk9FRtBUBkUr3mzU-q1lzk"],
]
);
class RandomUtil { class RandomUtil {
static randomIntRange(min, max) { static randomIntRange(min, max) {
@ -107,6 +132,14 @@ class RandomUtil {
return str; return str;
} }
static randomShortIdSeq(count) {
let str = '';
for (let i = 0; i < count; ++i) {
str += shortIdSeq[this.randomInt(16)];
}
return str;
}
static randomLowerAndNum(count) { static randomLowerAndNum(count) {
let str = ''; let str = '';
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
@ -137,6 +170,26 @@ class RandomUtil {
}); });
} }
static randowShortId() {
let str = '';
str += this.randomShortIdSeq(8)
return str;
}
static randomX25519PrivateKey() {
let num = x25519Map.size;
let index = this.randomInt(num);
let cntr = 0;
for (let key of x25519Map.keys()) {
if (cntr++ === index) {
return key;
}
}
}
static randomX25519PublicKey(key) {
return x25519Map.get(key)
}
static randomText() { static randomText() {
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
var string = ''; var string = '';

View file

@ -3,77 +3,74 @@ package controller
import "github.com/gin-gonic/gin" import "github.com/gin-gonic/gin"
type APIController struct { type APIController struct {
BaseController BaseController
inboundController *InboundController inboundController *InboundController
settingController *SettingController
} }
func NewAPIController(g *gin.RouterGroup) *APIController { func NewAPIController(g *gin.RouterGroup) *APIController {
a := &APIController{} a := &APIController{}
a.initRouter(g) a.initRouter(g)
return a return a
} }
func (a *APIController) initRouter(g *gin.RouterGroup) { func (a *APIController) initRouter(g *gin.RouterGroup) {
g = g.Group("/xui/API/inbounds") g = g.Group("/xui/API/inbounds")
g.Use(a.checkLogin) g.Use(a.checkLogin)
g.POST("/list", a.getAllInbounds) g.GET("/list", a.getAllInbounds)
g.GET("/get/:id", a.getSingleInbound) g.GET("/get/:id", a.getSingleInbound)
g.POST("/add", a.addInbound) g.POST("/add", a.addInbound)
g.POST("/del/:id", a.delInbound) g.POST("/del/:id", a.delInbound)
g.POST("/update/:id", a.updateInbound) g.POST("/update/:id", a.updateInbound)
g.POST("/clientIps/:email", a.getClientIps) g.POST("/clientIps/:email", a.getClientIps)
g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/clearClientIps/:email", a.clearClientIps)
g.POST("/addClient/", a.addInboundClient) g.POST("/addClient/", a.addInboundClient)
g.POST("/delClient/:email", a.delInboundClient) g.POST("/delClient/:email", a.delInboundClient)
g.POST("/updateClient/:index", a.updateInboundClient) g.POST("/updateClient/:index", a.updateInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
a.inboundController = NewInboundController(g) a.inboundController = NewInboundController(g)
} }
func (a *APIController) getAllInbounds(c *gin.Context) { func (a *APIController) getAllInbounds(c *gin.Context) {
a.inboundController.getInbounds(c) a.inboundController.getInbounds(c)
} }
func (a *APIController) getSingleInbound(c *gin.Context) { func (a *APIController) getSingleInbound(c *gin.Context) {
a.inboundController.getInbound(c) a.inboundController.getInbound(c)
} }
func (a *APIController) addInbound(c *gin.Context) { func (a *APIController) addInbound(c *gin.Context) {
a.inboundController.addInbound(c) a.inboundController.addInbound(c)
} }
func (a *APIController) delInbound(c *gin.Context) { func (a *APIController) delInbound(c *gin.Context) {
a.inboundController.delInbound(c) a.inboundController.delInbound(c)
} }
func (a *APIController) updateInbound(c *gin.Context) { func (a *APIController) updateInbound(c *gin.Context) {
a.inboundController.updateInbound(c) a.inboundController.updateInbound(c)
} }
func (a *APIController) getClientIps(c *gin.Context) { func (a *APIController) getClientIps(c *gin.Context) {
a.inboundController.getClientIps(c) a.inboundController.getClientIps(c)
} }
func (a *APIController) clearClientIps(c *gin.Context) { func (a *APIController) clearClientIps(c *gin.Context) {
a.inboundController.clearClientIps(c) a.inboundController.clearClientIps(c)
} }
func (a *APIController) addInboundClient(c *gin.Context) { func (a *APIController) addInboundClient(c *gin.Context) {
a.inboundController.addInboundClient(c) a.inboundController.addInboundClient(c)
} }
func (a *APIController) delInboundClient(c *gin.Context) { func (a *APIController) delInboundClient(c *gin.Context) {
a.inboundController.delInboundClient(c) a.inboundController.delInboundClient(c)
} }
func (a *APIController) updateInboundClient(c *gin.Context) { func (a *APIController) updateInboundClient(c *gin.Context) {
a.inboundController.updateInboundClient(c) a.inboundController.updateInboundClient(c)
} }
func (a *APIController) resetClientTraffic(c *gin.Context) { func (a *APIController) resetClientTraffic(c *gin.Context) {
a.inboundController.resetClientTraffic(c) a.inboundController.resetClientTraffic(c)
}
func (a *APIController) resetAllTraffics(c *gin.Context) {
a.inboundController.resetAllTraffics(c)
}
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
a.inboundController.resetAllClientTraffics(c)
} }

View file

@ -37,6 +37,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/delClient/:email", a.delInboundClient) g.POST("/delClient/:email", a.delInboundClient)
g.POST("/updateClient/:index", a.updateInboundClient) g.POST("/updateClient/:index", a.updateInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
} }
@ -131,7 +133,7 @@ func (a *InboundController) updateInbound(c *gin.Context) {
func (a *InboundController) getClientIps(c *gin.Context) { func (a *InboundController) getClientIps(c *gin.Context) {
email := c.Param("email") email := c.Param("email")
ips , err := a.inboundService.GetInboundClientIps(email) ips, err := a.inboundService.GetInboundClientIps(email)
if err != nil { if err != nil {
jsonObj(c, "No IP Record", nil) jsonObj(c, "No IP Record", nil)
return return
@ -230,3 +232,27 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }
func (a *InboundController) resetAllTraffics(c *gin.Context) {
err := a.inboundService.ResetAllTraffics()
if err != nil {
jsonMsg(c, "something worng!", err)
return
}
jsonMsg(c, "All traffics reseted", nil)
}
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
return
}
err = a.inboundService.ResetAllClientTraffics(id)
if err != nil {
jsonMsg(c, "something worng!", err)
return
}
jsonMsg(c, "All traffics of client reseted", nil)
}

View file

@ -38,7 +38,9 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/stopXrayService", a.stopXrayService) g.POST("/stopXrayService", a.stopXrayService)
g.POST("/restartXrayService", a.restartXrayService) g.POST("/restartXrayService", a.restartXrayService)
g.POST("/installXray/:version", a.installXray) g.POST("/installXray/:version", a.installXray)
g.POST("/logs", a.getLogs) g.POST("/logs/:count", a.getLogs)
g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb)
} }
func (a *ServerController) refreshStatus() { func (a *ServerController) refreshStatus() {
@ -109,10 +111,34 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
} }
func (a *ServerController) getLogs(c *gin.Context) { func (a *ServerController) getLogs(c *gin.Context) {
logs, err := a.serverService.GetLogs() count := c.Param("count")
logs, err := a.serverService.GetLogs(count)
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err) jsonMsg(c, I18n(c, "getLogs"), err)
return return
} }
jsonObj(c, logs, nil) jsonObj(c, logs, nil)
} }
func (a *ServerController) getConfigJson(c *gin.Context) {
configJson, err := a.serverService.GetConfigJson()
if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err)
return
}
jsonObj(c, configJson, nil)
}
func (a *ServerController) getDb(c *gin.Context) {
db, err := a.serverService.GetDb()
if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err)
return
}
// Set the headers for the response
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename=xui.db")
// Write the file contents to the response
c.Writer.Write(db)
}

View file

@ -33,6 +33,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
g = g.Group("/setting") g = g.Group("/setting")
g.POST("/all", a.getAllSetting) g.POST("/all", a.getAllSetting)
g.POST("/defaultSettings", a.getDefaultSettings)
g.POST("/update", a.updateSetting) g.POST("/update", a.updateSetting)
g.POST("/updateUser", a.updateUser) g.POST("/updateUser", a.updateUser)
g.POST("/restartPanel", a.restartPanel) g.POST("/restartPanel", a.restartPanel)
@ -47,6 +48,36 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
jsonObj(c, allSetting, nil) jsonObj(c, allSetting, nil)
} }
func (a *SettingController) getDefaultSettings(c *gin.Context) {
expireDiff, err := a.settingService.GetExpireDiff()
if err != nil {
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
return
}
trafficDiff, err := a.settingService.GetTrafficDiff()
if err != nil {
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
return
}
defaultCert, err := a.settingService.GetCertFile()
if err != nil {
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
return
}
defaultKey, err := a.settingService.GetKeyFile()
if err != nil {
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
return
}
result := map[string]interface{}{
"expireDiff": expireDiff,
"trafficDiff": trafficDiff,
"defaultCert": defaultCert,
"defaultKey": defaultKey,
}
jsonObj(c, result, nil)
}
func (a *SettingController) updateSetting(c *gin.Context) { func (a *SettingController) updateSetting(c *gin.Context) {
allSetting := &entity.AllSetting{} allSetting := &entity.AllSetting{}
err := c.ShouldBind(allSetting) err := c.ShouldBind(allSetting)

42
web/controller/sub.go Normal file
View file

@ -0,0 +1,42 @@
package controller
import (
"encoding/base64"
"strings"
"x-ui/web/service"
"github.com/gin-gonic/gin"
)
type SUBController struct {
BaseController
subService service.SubService
}
func NewSUBController(g *gin.RouterGroup) *SUBController {
a := &SUBController{}
a.initRouter(g)
return a
}
func (a *SUBController) initRouter(g *gin.RouterGroup) {
g = g.Group("/sub")
g.GET("/:subid", a.subs)
}
func (a *SUBController) subs(c *gin.Context) {
subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0]
subs, err := a.subService.GetSubs(subId, host)
if err != nil {
c.String(400, "Error!")
} else {
result := ""
for _, sub := range subs {
result += sub + "\n"
}
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
}
}

View file

@ -32,13 +32,13 @@ type AllSetting struct {
WebCertFile string `json:"webCertFile" form:"webCertFile"` WebCertFile string `json:"webCertFile" form:"webCertFile"`
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
WebBasePath string `json:"webBasePath" form:"webBasePath"` WebBasePath string `json:"webBasePath" form:"webBasePath"`
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"` TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"` TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
TgExpireDiff int `json:"tgExpireDiff" form:"tgExpireDiff"`
TgTrafficDiff int `json:"tgTrafficDiff" form:"tgTrafficDiff"`
TgCpu int `json:"tgCpu" form:"tgCpu"` TgCpu int `json:"tgCpu" form:"tgCpu"`
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"` XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
TimeLocation string `json:"timeLocation" form:"timeLocation"` TimeLocation string `json:"timeLocation" form:"timeLocation"`

View file

@ -7,6 +7,7 @@
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.css"> <link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.css">
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css"> <link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<style> <style>
[v-cloak] { [v-cloak] {
display: none; display: none;

View file

@ -10,8 +10,7 @@
<a-select-option :value="1">Random+Prefix</a-select-option> <a-select-option :value="1">Random+Prefix</a-select-option>
<a-select-option :value="2">Random+Prefix+Num</a-select-option> <a-select-option :value="2">Random+Prefix+Num</a-select-option>
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option> <a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-select-option :value="4">Random+Prefix+Num@Telegram Username</a-select-option> <a-select-option :value="4">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option>
<a-select-option :value="5">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option>
</a-select> </a-select>
</a-form-item><br /> </a-form-item><br />
<a-form-item v-if="clientsBulkModal.emailMethod>1"> <a-form-item v-if="clientsBulkModal.emailMethod>1">
@ -27,15 +26,19 @@
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 120px"></a-input> <a-input v-model="clientsBulkModal.emailPrefix" style="width: 120px"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="clientsBulkModal.emailMethod>2"> <a-form-item v-if="clientsBulkModal.emailMethod>2">
<span slot="label" v-if="clientsBulkModal.emailMethod == 4">tg_uname</span> <span slot="label">{{ i18n "pages.client.postfix" }}</span>
<span slot="label" v-else>{{ i18n "pages.client.postfix" }}</span>
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 120px"></a-input> <a-input v-model="clientsBulkModal.emailPostfix" style="width: 120px"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="clientsBulkModal.emailMethod < 2"> <a-form-item v-if="clientsBulkModal.emailMethod < 2">
<span slot="label">{{ i18n "pages.client.clientCount" }}</span> <span slot="label">{{ i18n "pages.client.clientCount" }}</span>
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number> <a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="Subscription">
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
</a-form-item>
<a-form-item label="Telegram ID">
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
</a-form-item>
<a-form-item> <a-form-item>
<span slot="label"> <span slot="label">
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
@ -48,7 +51,13 @@
</span> </span>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> <a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item label="{{ i18n "pages.client.delayedStart" }}">
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item>
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientsBulkModal.delayedStart">
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
</a-form-item>
<a-form-item v-else>
<span slot="label"> <span slot="label">
<span >{{ i18n "pages.inbounds.expireDate" }}</span> <span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip> <a-tooltip>
@ -83,6 +92,9 @@
lastNum: 1, lastNum: 1,
emailPrefix: "", emailPrefix: "",
emailPostfix: "", emailPostfix: "",
subId: "",
tgId: "",
delayedStart: false,
ok() { ok() {
method=clientsBulkModal.emailMethod; method=clientsBulkModal.emailMethod;
if(method>1){ if(method>1){
@ -94,11 +106,13 @@
} }
prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : ""; prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : "";
useNum=(method>1); useNum=(method>1);
postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? (method == 4 ? "@" : "") + clientsBulkModal.emailPostfix : ""; postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? clientsBulkModal.emailPostfix : "";
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol); newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
if(method==5) newClient.email = ""; if(method==4) newClient.email = "";
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix; newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
newClient.subId = clientsBulkModal.subId;
newClient.tgId = clientsBulkModal.tgId;
newClient._totalGB = clientsBulkModal.totalGB; newClient._totalGB = clientsBulkModal.totalGB;
newClient._expiryTime = clientsBulkModal.expiryTime; newClient._expiryTime = clientsBulkModal.expiryTime;
clientsBulkModal.clients.push(newClient); clientsBulkModal.clients.push(newClient);
@ -112,16 +126,18 @@
this.confirm = confirm; this.confirm = confirm;
this.quantity = 1; this.quantity = 1;
this.totalGB = 0; this.totalGB = 0;
this.expiryTime = ''; this.expiryTime = 0;
this.emailMethod= 0; this.emailMethod= 0;
this.firstNum= 1; this.firstNum= 1;
this.lastNum= 1; this.lastNum= 1;
this.emailPrefix= ""; this.emailPrefix= "";
this.emailPostfix= ""; this.emailPostfix= "";
this.subId= "";
this.tgId= "";
this.dbInbound = new DBInbound(dbInbound); this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings); this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
this.delayedStart = false;
}, },
getClients(protocol, clientSettings) { getClients(protocol, clientSettings) {
switch(protocol){ switch(protocol){
@ -156,6 +172,12 @@
get inbound() { get inbound() {
return this.clientsBulkModal.inbound; return this.clientsBulkModal.inbound;
}, },
get delayedExpireDays() {
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0;
},
set delayedExpireDays(days){
this.clientsBulkModal.expiryTime = -86400000 * days;
},
}, },
}); });
</script> </script>

View file

@ -1,7 +1,7 @@
{{define "clientsModal"}} {{define "clientsModal"}}
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok" <a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false" :confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
:class="siderDrawer.isDarkTheme ? darkClass : ''" :class="siderDrawer.isDarkTheme ? darkClass : ''"
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'> :ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
{{template "form/client"}} {{template "form/client"}}
</a-modal> </a-modal>
@ -19,6 +19,7 @@
index: null, index: null,
clientIps: null, clientIps: null,
isExpired: false, isExpired: false,
delayedStart: false,
ok() { ok() {
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index); ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index);
}, },
@ -32,8 +33,13 @@
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings); this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
this.index = index === null ? this.clients.length : index; this.index = index === null ? this.clients.length : index;
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false; this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
this.delayedStart = false;
if (!isEdit){ if (!isEdit){
this.addClient(this.inbound.protocol, this.clients); this.addClient(this.inbound.protocol, this.clients);
} else {
if (this.clients[index].expiryTime < 0){
this.delayedStart = true;
}
} }
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email); this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
this.confirm = confirm; this.confirm = confirm;
@ -82,7 +88,7 @@
}, },
get isTrafficExhausted() { get isTrafficExhausted() {
if(!clientStats) return false if(!clientStats) return false
if(clientStats.total == 0) return false if(clientStats.total <= 0) return false
if(clientStats.up + clientStats.down < clientStats.total) return false if(clientStats.up + clientStats.down < clientStats.total) return false
return true return true
}, },
@ -91,10 +97,16 @@
}, },
get statsColor() { get statsColor() {
if(!clientStats) return 'blue' if(!clientStats) return 'blue'
if(clientStats.total === 0) return 'blue' if(clientStats.total <= 0) return 'blue'
else if(clientStats.total > 0 && (clientStats.down+clientStats.up) < clientStats.total) return 'cyan' else if(clientStats.total > 0 && (clientStats.down+clientStats.up) < clientStats.total) return 'cyan'
else return 'red' else return 'red'
} },
get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
},
set delayedExpireDays(days){
this.client.expiryTime = -86400000 * days;
},
}, },
methods: { methods: {
getNewEmail(client) { getNewEmail(client) {

View file

@ -15,6 +15,9 @@
</span> </span>
<a-input v-model.trim="client.email" style="width: 150px;" ></a-input> <a-input v-model.trim="client.email" style="width: 150px;" ></a-input>
</a-form-item> </a-form-item>
<a-form-item label="{{ i18n "pages.inbounds.enable" }}">
<a-switch v-model="client.enable"></a-switch>
</a-form-item>
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN"> <a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN">
<a-input v-model.trim="client.password" style="width: 150px;" ></a-input> <a-input v-model.trim="client.password" style="width: 150px;" ></a-input>
</a-form-item> </a-form-item>
@ -23,6 +26,12 @@
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS"> <a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
<a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input> <a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input>
</a-form-item>
<a-form-item label="Subscription" v-if="client.email">
<a-input v-model.trim="client.subId"></a-input>
</a-form-item>
<a-form-item label="Telegram Username" v-if="client.email">
<a-input v-model.trim="client.tgId"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<span slot="label"> <span slot="label">
@ -60,7 +69,7 @@
</a-form> </a-form>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.XTLS" label="Flow"> <a-form-item v-if="inbound.XTLS" label="Flow">
<a-select v-model="client.flow" style="width: 150px"> <a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="">{{ i18n "none" }}</a-select-option> <a-select-option value="">{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
@ -83,7 +92,7 @@
</span> </span>
<a-input-number v-model="client._totalGB":min="0" style="width: 70px;"></a-input-number> <a-input-number v-model="client._totalGB":min="0" style="width: 70px;"></a-input-number>
<template v-if="isEdit && clientStats"> <template v-if="isEdit && clientStats">
{{ i18n "usage" }}: <span>{{ i18n "usage" }}:</span>
<a-tag :color="statsColor"> <a-tag :color="statsColor">
[[ sizeFormat(clientStats.up) ]] / [[ sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]] [[ sizeFormat(clientStats.down) ]]
@ -91,7 +100,13 @@
</a-tag> </a-tag>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item label="{{ i18n "pages.client.delayedStart" }}">
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientModal.delayedStart">
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
</a-form-item>
<a-form-item v-else>
<span slot="label"> <span slot="label">
<span >{{ i18n "pages.inbounds.expireDate" }}</span> <span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip> <a-tooltip>
@ -102,8 +117,8 @@
</a-tooltip> </a-tooltip>
</span> </span>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 170px;"></a-date-picker> v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
<a-tag color="red" v-if="isExpiry">Expired</a-tag> <a-tag color="red" v-if="isExpiry">Expired</a-tag>
</a-form-item> </a-form-item>
</a-form> </a-form>

View file

@ -32,7 +32,7 @@
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input> <a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.XTLS" label="Flow"> <a-form-item v-if="inbound.XTLS" label="Flow">
<a-select v-model="client.flow" style="width: 150px"> <a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="">{{ i18n "none" }}</a-select-option> <a-select-option value="">{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
@ -60,6 +60,7 @@
</a-tooltip> </a-tooltip>
</span> </span>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 170px;"></a-date-picker> v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
</a-form-item> </a-form-item>
</a-collapse-panel> </a-collapse-panel>
@ -76,7 +77,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">
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-row> <a-row>

View file

@ -20,25 +20,25 @@
<a-input v-model.trim="client.id" style="width: 300px;" ></a-input> <a-input v-model.trim="client.id" style="width: 300px;" ></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<span slot="label"> <span slot="label">
<span>{{ i18n "pages.inbounds.IPLimit" }}</span> <span>{{ i18n "pages.inbounds.IPLimit" }}</span>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span> <span>{{ i18n "pages.inbounds.IPLimitDesc" }}</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> </span>
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input> <a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.XTLS" label="Flow"> <a-form-item v-if="inbound.XTLS" label="Flow">
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px"> <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow" layout="inline"> <a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow" layout="inline">
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px"> <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
@ -66,6 +66,7 @@
</a-tooltip> </a-tooltip>
</span> </span>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 170px;"></a-date-picker> v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
</a-form-item> </a-form-item>
</a-collapse-panel> </a-collapse-panel>
@ -82,7 +83,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">
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-row> <a-row>

View file

@ -57,7 +57,8 @@
</a-tooltip> </a-tooltip>
</span> </span>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
v-model="client._expiryTime" style="width: 170px;"></a-date-picker> :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
</a-form-item> </a-form-item>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>

View file

@ -4,7 +4,7 @@
<a-form-item label="AcceptProxyProtocol"> <a-form-item label="AcceptProxyProtocol">
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch> <a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="HTTP Camouflage"> <a-form-item label="HTTP {{ i18n "camouflage" }}">
<a-switch <a-switch
:checked="inbound.stream.tcp.type === 'http'" :checked="inbound.stream.tcp.type === 'http'"
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'"> @change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">

View file

@ -1,11 +1,32 @@
{{define "form/tlsSettings"}} {{define "form/tlsSettings"}}
<!-- tls enable --> <!-- tls enable -->
<a-form layout="inline" v-if="inbound.canSetTls()"> <a-form layout="inline" v-if="inbound.canSetTls()">
<a-form-item label="TLS"> <a-form-item v-if="inbound.canEnableTls()" 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()">
<span slot="label">
Reality
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.Realitydec" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</span>
<a-switch v-model="inbound.reality"></a-switch>
</a-form-item>
<a-form-item v-if="inbound.canEnableXTLS()">
<span slot="label">
XTLS
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.XTLSdec" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</span>
<a-switch v-model="inbound.XTLS"></a-switch> <a-switch v-model="inbound.XTLS"></a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -61,6 +82,7 @@
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'> <a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input> <a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input>
</a-form-item> </a-form-item>
<a-button @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</template> </template>
<template v-else> <template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'> <a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
@ -71,4 +93,33 @@
</a-form-item> </a-form-item>
</template> </template>
</a-form> </a-form>
<a-form v-else-if="inbound.reality" layout="inline">
<a-form-item label="show">
<a-switch v-model="inbound.stream.reality.show">
</a-switch>
</a-form-item>
<a-form-item label="xver">
<a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input>
</a-form-item>
<a-form-item label="uTLS" >
<a-select v-model="inbound.stream.reality.fingerprint" style="width: 135px">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="dest">
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 360px"></a-input>
</a-form-item>
<a-form-item label="serverNames">
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 360px"></a-input>
</a-form-item>
<a-form-item label="privateKey">
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 360px"></a-input>
</a-form-item>
<a-form-item label="publicKey">
<a-input v-model.trim="inbound.stream.reality.publicKey" style="width: 360px"></a-input>
</a-form-item>
<a-form-item label="shortIds">
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
</a-form-item>
</a-form>
{{end}} {{end}}

View file

@ -21,9 +21,12 @@
<a-icon style="font-size: 24px;" type="delete" v-if="isRemovable(record.id)" @click="delClient(record.id,client)"></a-icon> <a-icon style="font-size: 24px;" type="delete" v-if="isRemovable(record.id)" @click="delClient(record.id,client)"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<template slot="enable" slot-scope="text, client, index">
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
</template>
<template slot="client" slot-scope="text, client"> <template slot="client" slot-scope="text, client">
[[ client.email ]] [[ client.email ]]
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "disabled" }}</a-tag> <a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
</template> </template>
<template slot="traffic" slot-scope="text, client"> <template slot="traffic" slot-scope="text, client">
<a-tag color="blue">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag> <a-tag color="blue">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
@ -34,11 +37,12 @@
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag> <a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
</template> </template>
<template slot="expiryTime" slot-scope="text, client, index"> <template slot="expiryTime" slot-scope="text, client, index">
<template v-if="client._expiryTime > 0"> <template v-if="client.expiryTime > 0">
<a-tag :color="isExpiry(record, index)? 'red' : 'blue'"> <a-tag :color="isExpiry(record, index)? 'red' : 'blue'">
[[ DateUtil.formatMillis(client._expiryTime) ]] [[ DateUtil.formatMillis(client._expiryTime) ]]
</a-tag> </a-tag>
</template> </template>
<a-tag v-else-if="client.expiryTime < 0" color="cyan">[[ client._expiryTime ]] {{ i18n "pages.client.days" }}</a-tag>
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag> <a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
</template> </template>
{{end}} {{end}}

View file

@ -49,7 +49,7 @@
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td> </td>
<td v-else-if="inbound.xtls"> <td v-else-if="inbound.XTLS">
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td> </td>
@ -59,13 +59,25 @@
</table> </table>
<template v-if="infoModal.clientSettings"> <template v-if="infoModal.clientSettings">
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider> <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<table style="margin-bottom: 10px;"> <table style="margin-bottom: 10px;">
<tr v-for="col,index in Object.keys(infoModal.clientSettings).slice(0, 3)"> <tr v-for="col,index in Object.keys(infoModal.clientSettings).slice(0, 3)">
<td>[[ col ]]</td> <td>[[ col ]]</td>
<td><a-tag color="green">[[ infoModal.clientSettings[col] ]]</a-tag></td> <td><a-tag color="green">[[ infoModal.clientSettings[col] ]]</a-tag></td>
</table> </tr>
<tr>
<td>{{ i18n "status" }}</td>
<td>
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
</td>
</tr>
</table>
<table style="margin-bottom: 10px; width: 100%;"> <table style="margin-bottom: 10px; width: 100%;">
<tr><th>{{ i18n "usage" }}</th><th>{{ i18n "pages.inbounds.totalFlow" }}</th><th>{{ i18n "pages.inbounds.expireDate" }}</th><th>{{ i18n "enable" }}</th></tr> <tr>
<th>{{ i18n "usage" }}</th>
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
<tr> <tr>
<td> <td>
<a-tag v-if="infoModal.clientStats" :color="statsColor(infoModal.clientStats)"> <a-tag v-if="infoModal.clientStats" :color="statsColor(infoModal.clientStats)">
@ -84,12 +96,19 @@
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]] [[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
</a-tag> </a-tag>
</template> </template>
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="cyan">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag>
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag> <a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
</td> </td>
<td> </tr>
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag> </table>
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag> <table v-if="infoModal.clientSettings.subId + infoModal.clientSettings.tgId" style="margin-bottom: 10px;">
</td> <tr v-if="infoModal.clientSettings.subId">
<td>Subscription link</td>
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
</tr>
<tr v-if="infoModal.clientSettings.tgId">
<td>Telegram Username</td>
<td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
</tr> </tr>
</table> </table>
</template> </template>
@ -160,13 +179,12 @@
</div> </div>
</a-modal> </a-modal>
<script> <script>
const infoModal = { const infoModal = {
visible: false, visible: false,
inbound: new Inbound(), inbound: new Inbound(),
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
settings: null, settings: null,
clientSettings: new Inbound.Settings(), clientSettings: null,
clientStats: [], clientStats: [],
upStats: 0, upStats: 0,
downStats: 0, downStats: 0,
@ -209,12 +227,24 @@
get inbound() { get inbound() {
return this.infoModal.inbound; return this.infoModal.inbound;
}, },
get isEnable() { get isActive() {
if(infoModal.clientStats){ if(infoModal.clientStats){
return infoModal.clientStats.enable; return infoModal.clientStats.enable;
} }
return infoModal.dbInbound.isEnable; return infoModal.dbInbound.isEnable;
} },
get isEnable() {
if(infoModal.clientSettings){
return infoModal.clientSettings.enable;
}
return infoModal.dbInbound.isEnable;
},
get subBase() {
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + basePath + "sub/";
},
get tgBase() {
return "https://t.me/"
},
}, },
methods: { methods: {
copyTextToClipboard(elmentId,content) { copyTextToClipboard(elmentId,content) {

View file

@ -96,6 +96,10 @@
clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null
return clientStats ? clientStats['enable'] : true return clientStats ? clientStats['enable'] : true
}, },
setDefaultCertData(){
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
},
getNewEmail(client) { getNewEmail(client) {
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
var string = ''; var string = '';

View file

@ -41,8 +41,24 @@
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
{{ i18n "clients" }}: {{ i18n "clients" }}:
<a-tag color="green">[[ total.clients ]]</a-tag> <a-tag color="green">[[ total.clients ]]</a-tag>
<a-tag color="blue">{{ i18n "enabled" }} [[ total.active ]]</a-tag> <a-popover title="{{ i18n "disabled" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<a-tag color="red">{{ i18n "disabled" }} [[ total.deactive ]]</a-tag> <template slot="content">
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
</template>
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
</a-popover>
<a-popover title="{{ i18n "depleted" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<template slot="content">
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
</template>
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
</a-popover>
<a-popover title="{{ i18n "depletingSoon" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<template slot="content">
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
</template>
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
</a-popover>
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
@ -52,7 +68,7 @@
<div slot="title"> <div slot="title">
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button> <a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
<a-button type="primary" icon="export" @click="exportAllLinks">{{ i18n "pages.inbounds.export" }}</a-button> <a-button type="primary" icon="export" @click="exportAllLinks">{{ i18n "pages.inbounds.export" }}</a-button>
<a-button type="primary" icon="reload" @click="resetAllTraffic">{{ i18n "pages.inbounds.resetAllTraffic" }}</a-button> <a-button type="primary" icon="reload" @click="resetAllTraffic">{{ i18n "pages.inbounds.resetAllTraffic" }}</a-button>
</div> </div>
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input> <a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id" <a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
@ -64,8 +80,8 @@
<template slot="action" slot-scope="text, dbInbound"> <template slot="action" slot-scope="text, dbInbound">
<a-icon type="edit" style="font-size: 25px" @click="openEditInbound(dbInbound.id);"></a-icon> <a-icon type="edit" style="font-size: 25px" @click="openEditInbound(dbInbound.id);"></a-icon>
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a> <a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme" style="border: 1px solid rgba(255, 255, 255, 0.65);"> <a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
<a-menu-item v-if="dbInbound.isSS" key="qrcode"> <a-menu-item v-if="dbInbound.isSS" key="qrcode">
<a-icon type="qrcode"></a-icon> <a-icon type="qrcode"></a-icon>
{{ i18n "qrCode" }} {{ i18n "qrCode" }}
@ -76,13 +92,17 @@
</a-menu-item> </a-menu-item>
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess"> <template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess">
<a-menu-item key="addClient"> <a-menu-item key="addClient">
<a-icon type="user"></a-icon> <a-icon type="user-add"></a-icon>
{{ i18n "pages.client.add"}} {{ i18n "pages.client.add"}}
</a-menu-item> </a-menu-item>
<a-menu-item key="addBulkClient"> <a-menu-item key="addBulkClient">
<a-icon type="team"></a-icon> <a-icon type="usergroup-add"></a-icon>
{{ i18n "pages.client.bulk"}} {{ i18n "pages.client.bulk"}}
</a-menu-item> </a-menu-item>
<a-menu-item key="resetClients">
<a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetAllClientTraffics"}}
</a-menu-item>
<a-menu-item key="export"> <a-menu-item key="export">
<a-icon type="export"></a-icon> <a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}} {{ i18n "pages.inbounds.export"}}
@ -97,7 +117,7 @@
<a-menu-item key="resetTraffic"> <a-menu-item key="resetTraffic">
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }} <a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="clone"> <a-menu-item key="clone">
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.Clone"}} <a-icon type="block"></a-icon> {{ i18n "pages.inbounds.Clone"}}
</a-menu-item> </a-menu-item>
<a-menu-item key="delete"> <a-menu-item key="delete">
@ -109,7 +129,36 @@
</a-dropdown> </a-dropdown>
</template> </template>
<template slot="protocol" slot-scope="text, dbInbound"> <template slot="protocol" slot-scope="text, dbInbound">
<a-tag color="blue">[[ dbInbound.protocol ]]</a-tag> <a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXTLS" color="cyan">XTLS</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">Reality</a-tag>
</template>
</template>
<template slot="clients" slot-scope="text, dbInbound">
<template v-if="clientCount[dbInbound.id]">
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
<a-popover title="{{ i18n "disabled" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
</template>
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
</a-popover>
<a-popover title="{{ i18n "depleted" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
</template>
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
</a-popover>
<a-popover title="{{ i18n "depletingSoon" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
</template>
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
</a-popover>
</template>
</template> </template>
<template slot="traffic" slot-scope="text, dbInbound"> <template slot="traffic" slot-scope="text, dbInbound">
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]] / [[ sizeFormat(dbInbound.down) ]]</a-tag> <a-tag color="blue">[[ sizeFormat(dbInbound.up) ]] / [[ sizeFormat(dbInbound.down) ]]</a-tag>
@ -119,14 +168,6 @@
</template> </template>
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag> <a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
</template> </template>
<template slot="stream" slot-scope="text, dbInbound, index">
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
<a-tag v-if="inbounds[index].stream.isTls" color="blue">tls</a-tag>
<a-tag v-if="inbounds[index].stream.isXTls" color="blue">xtls</a-tag>
</template>
<template v-else>{{ i18n "none" }}</template>
</template>
<template slot="enable" slot-scope="text, dbInbound"> <template slot="enable" slot-scope="text, dbInbound">
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch> <a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
</template> </template>
@ -191,26 +232,26 @@
align: 'center', align: 'center',
width: 80, width: 80,
dataIndex: "remark", dataIndex: "remark",
}, {
title: '{{ i18n "pages.inbounds.protocol" }}',
align: 'center',
width: 50,
scopedSlots: { customRender: 'protocol' },
}, { }, {
title: '{{ i18n "pages.inbounds.port" }}', title: '{{ i18n "pages.inbounds.port" }}',
align: 'center', align: 'center',
dataIndex: "port", dataIndex: "port",
width: 40, width: 40,
}, {
title: '{{ i18n "pages.inbounds.protocol" }}',
align: 'left',
width: 80,
scopedSlots: { customRender: 'protocol' },
}, {
title: '{{ i18n "clients" }}',
align: 'left',
width: 50,
scopedSlots: { customRender: 'clients' },
}, { }, {
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
align: 'center', align: 'center',
width: 150, width: 120,
scopedSlots: { customRender: 'traffic' }, scopedSlots: { customRender: 'traffic' },
}, {
title: '{{ i18n "pages.inbounds.transportConfig" }}',
align: 'center',
width: 60,
scopedSlots: { customRender: 'stream' },
}, { }, {
title: '{{ i18n "pages.inbounds.expireDate" }}', title: '{{ i18n "pages.inbounds.expireDate" }}',
align: 'center', align: 'center',
@ -220,17 +261,20 @@
const innerColumns = [ const innerColumns = [
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } }, { title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 60, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 100, scopedSlots: { customRender: 'traffic' } }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } }, { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
{ title: 'UID', width: 150, dataIndex: "id" }, { title: 'UID', width: 120, dataIndex: "id" },
]; ];
const innerTrojanColumns = [ const innerTrojanColumns = [
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } }, { title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 60, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 100, scopedSlots: { customRender: 'traffic' } }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } }, { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
{ title: 'Password', width: 100, dataIndex: "password" }, { title: 'Password', width: 120, dataIndex: "password" },
]; ];
const app = new Vue({ const app = new Vue({
@ -243,6 +287,11 @@
dbInbounds: [], dbInbounds: [],
searchKey: '', searchKey: '',
searchedInbounds: [], searchedInbounds: [],
expireDiff: 0,
trafficDiff: 0,
defaultCert: '',
defaultKey: '',
clientCount: {},
}, },
methods: { methods: {
loading(spinning=true) { loading(spinning=true) {
@ -258,17 +307,66 @@
this.setInbounds(msg.obj); this.setInbounds(msg.obj);
this.searchKey = ''; this.searchKey = '';
}, },
async getDefaultSettings() {
this.loading();
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
this.loading(false);
if (!msg.success) {
return;
}
this.expireDiff = msg.obj.expireDiff * 86400000;
this.trafficDiff = msg.obj.trafficDiff * 1073741824;
this.defaultCert = msg.obj.defaultCert;
this.defaultKey = msg.obj.defaultKey;
},
setInbounds(dbInbounds) { setInbounds(dbInbounds) {
this.inbounds.splice(0); this.inbounds.splice(0);
this.dbInbounds.splice(0); this.dbInbounds.splice(0);
this.searchedInbounds.splice(0); this.searchedInbounds.splice(0);
for (const inbound of dbInbounds) { for (const inbound of dbInbounds) {
const dbInbound = new DBInbound(inbound); const dbInbound = new DBInbound(inbound);
this.inbounds.push(dbInbound.toInbound()); to_inbound = dbInbound.toInbound()
this.inbounds.push(to_inbound);
this.dbInbounds.push(dbInbound); this.dbInbounds.push(dbInbound);
this.searchedInbounds.push(dbInbound); this.searchedInbounds.push(dbInbound);
if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){
this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound);
}
} }
}, },
getClientCounts(dbInbound,inbound){
let clientCount = 0,active = [], deactive = [], depleted = [], expiring = [];
clients = this.getClients(dbInbound.protocol, inbound.settings);
clientStats = dbInbound.clientStats
now = new Date().getTime()
if(clients){
clientCount = clients.length;
if(dbInbound.enable){
clients.forEach(client => {
client.enable ? active.push(client.email) : deactive.push(client.email);
});
clientStats.forEach(client => {
if(!client.enable) {
depleted.push(client.email);
} else {
if ((client.expiryTime > 0 && (client.expiryTime-now < this.expireDiff)) ||
(client.total > 0 && (client.total-(client.up+client.down) < this.trafficDiff ))) expiring.push(client.email);
}
});
} else {
clients.forEach(client => {
deactive.push(client.email);
});
}
}
return {
clients: clientCount,
active: active,
deactive: deactive,
depleted: depleted,
expiring: expiring,
};
},
searchInbounds(key) { searchInbounds(key) {
if (ObjectUtil.isEmpty(key)) { if (ObjectUtil.isEmpty(key)) {
this.searchedInbounds = this.dbInbounds.slice(); this.searchedInbounds = this.dbInbounds.slice();
@ -315,7 +413,10 @@
case "resetTraffic": case "resetTraffic":
this.resetTraffic(dbInbound.id); this.resetTraffic(dbInbound.id);
break; break;
case "clone": case "resetClients":
this.resetAllClientTraffics(dbInbound.id);
break;
case "clone":
this.openCloneInbound(dbInbound); this.openCloneInbound(dbInbound);
break; break;
case "delete": case "delete":
@ -477,7 +578,7 @@
id: dbInbound.id, id: dbInbound.id,
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
await this.submit('/xui/inbound/addClient', data); await this.submit('/xui/inbound/addClient/', data);
}, },
async updateClient(inbound, dbInbound, index) { async updateClient(inbound, dbInbound, index) {
const data = { const data = {
@ -501,22 +602,6 @@
this.updateInbound(inbound, dbInbound); this.updateInbound(inbound, dbInbound);
}, },
}); });
},
resetAllTraffic() {
this.$confirm({
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
okText: '{{ i18n "pages.inbounds.resetAllTrafficOkText"}}',
cancelText: '{{ i18n "pages.inbounds.resetAllTrafficCancelText"}}',
onOk: async () => {
for (const dbInbound of this.dbInbounds) {
const inbound = dbInbound.toInbound();
dbInbound.up = 0;
dbInbound.down = 0;
this.updateInbound(inbound, dbInbound);
}
},
});
}, },
delInbound(dbInboundId) { delInbound(dbInboundId) {
this.$confirm({ this.$confirm({
@ -567,6 +652,16 @@
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound); this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
}, },
async switchEnableClient(dbInboundId, client) {
this.loading()
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
inbound = dbInbound.toInbound();
clients = this.getClients(dbInbound.protocol, inbound.settings);
index = this.findIndexOfClient(clients, client);
clients[index].enable = ! clients[index].enable
await this.updateClient(inbound, dbInbound, index);
this.loading(false);
},
async submit(url, data) { async submit(url, data) {
const msg = await HttpUtil.postWithModal(url, data); const msg = await HttpUtil.postWithModal(url, data);
if (msg.success) { if (msg.success) {
@ -592,6 +687,26 @@
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ client.email), onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ client.email),
}) })
}, },
resetAllTraffic() {
this.$confirm({
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
class: siderDrawer.isDarkTheme ? darkClass : '',
okText: '{{ i18n "reset"}}',
cancelText: '{{ i18n "cancel"}}',
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
});
},
resetAllClientTraffics(dbInboundId) {
this.$confirm({
title: '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
content: '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
class: siderDrawer.isDarkTheme ? darkClass : '',
okText: '{{ i18n "reset"}}',
cancelText: '{{ i18n "cancel"}}',
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
})
},
isExpiry(dbInbound, index) { isExpiry(dbInbound, index) {
return dbInbound.toInbound().isExpiry(index) return dbInbound.toInbound().isExpiry(index)
}, },
@ -635,37 +750,30 @@
}, 500) }, 500)
}, },
mounted() { mounted() {
this.getDefaultSettings();
this.getDBInbounds(); this.getDBInbounds();
}, },
computed: { computed: {
total() { total() {
let down = 0, up = 0; let down = 0, up = 0;
let clients = 0, active = 0, deactive = 0; let clients = 0, deactive = [], depleted = [], expiring = [];
this.dbInbounds.forEach(dbInbound => { this.dbInbounds.forEach(dbInbound => {
down += dbInbound.down; down += dbInbound.down;
up += dbInbound.up; up += dbInbound.up;
inbound = dbInbound.toInbound(); if (this.clientCount[dbInbound.id]) {
clients = this.getClients(dbInbound.protocol, inbound.settings); clients += this.clientCount[dbInbound.id].clients;
if(clients){ deactive = deactive.concat(this.clientCount[dbInbound.id].deactive);
if(dbInbound.enable){ depleted = depleted.concat(this.clientCount[dbInbound.id].depleted);
isClientEnable = false; expiring = expiring.concat(this.clientCount[dbInbound.id].expiring);
clients.forEach(client => {
isClientEnable = client.email == "" ? true: this.isClientEnabled(dbInbound,client.email);
isClientEnable ? active++ : deactive++;
});
} else {
deactive += clients.length;
}
} else {
dbInbound.enable ? active++ : deactive++;
} }
}); });
return { return {
down: down, down: down,
up: up, up: up,
clients: active + deactive, clients: clients,
active: active,
deactive: deactive, deactive: deactive,
depleted: depleted,
expiring: expiring,
}; };
} }
}, },

View file

@ -76,18 +76,9 @@
<a-row> <a-row>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
{{ i18n "pages.index.xrayStatus" }}: 3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag> Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
<a-tooltip v-if="status.xray.state === State.Error"> Telegram: <a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
<template slot="title">
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
<a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
@ -102,6 +93,29 @@
</a-tooltip> </a-tooltip>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12">
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
{{ i18n "pages.index.xrayStatus" }}:
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
<a-tooltip v-if="status.xray.state === State.Error">
<template slot="title">
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
{{ i18n "menu.link" }}:
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Log Reports</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] {{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
@ -169,13 +183,6 @@
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">Telegram</a-tag></a>
<a-tag color="blue" style="cursor: pointer;" @click="openLogs">Log Reports</a-tag>
</a-card>
</a-col> </a-col>
</a-row> </a-row>
</transition> </transition>
@ -199,14 +206,34 @@
:class="siderDrawer.isDarkTheme ? darkClass : ''" :class="siderDrawer.isDarkTheme ? darkClass : ''"
width="800px" width="800px"
footer=""> footer="">
<table style="margin: 0px; width: 100%; background-color: black; color: hsla(0,0%,100%,.65);"> <a-form layout="inline">
<tr v-for="log , index in logModal.logs"> <a-form-item label="Count">
<td style="vertical-align: top;">[[ index ]]</td><td>[[ log ]]</td> <a-select v-model="logModal.rows"
</tr> style="width: 80px"
</table> @change="openLogs(logModal.rows)"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="10">10</a-select-option>
<a-select-option value="20">20</a-select-option>
<a-select-option value="50">50</a-select-option>
<a-select-option value="100">100</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<button class="ant-btn ant-btn-primary" @click="openLogs(logModal.rows)"><a-icon type="sync"></a-icon> Reload</button>
</a-form-item>
<a-form-item>
<a-button type="primary" style="margin-bottom: 10px;"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" download="x-ui.log">
{{ i18n "download" }} x-ui.log
</a-button>
</a-form-item>
</a-form>
<a-input type="textarea" v-model="logModal.logs" disabled="true"
:autosize="{ minRows: 10, maxRows: 22}"></a-input>
</a-modal> </a-modal>
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
{{template "textModal"}}
<script> <script>
const State = { const State = {
@ -301,9 +328,11 @@
const logModal = { const logModal = {
visible: false, visible: false,
logs: '', logs: '',
show(logs) { rows: 20,
show(logs, rows) {
this.visible = true; this.visible = true;
this.logs = logs; this.rows = rows;
this.logs = logs.join("\n");
}, },
hide() { hide() {
this.visible = false; this.visible = false;
@ -377,14 +406,26 @@
return; return;
} }
}, },
async openLogs(){ async openLogs(rows){
this.loading(true); this.loading(true);
const msg = await HttpUtil.post('server/logs'); const msg = await HttpUtil.post('server/logs/'+rows);
this.loading(false); this.loading(false);
if (!msg.success) { if (!msg.success) {
return; return;
} }
logModal.show(msg.obj); logModal.show(msg.obj,rows);
},
async openConfig(){
this.loading(true);
const msg = await HttpUtil.post('server/getConfigJson');
this.loading(false);
if (!msg.success) {
return;
}
txtModal.show('config.json',JSON.stringify(msg.obj, null, 2),'config.json');
},
getBackup(){
window.location = basePath + 'server/getDb';
} }
}, },
async mounted() { async mounted() {

View file

@ -44,6 +44,8 @@
<setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.setting.expireTimeDiff" }}' desc='{{ i18n "pages.setting.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.setting.trafficDiff" }}' desc='{{ i18n "pages.setting.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
<a-list-item> <a-list-item>
<a-row style="padding: 20px"> <a-row style="padding: 20px">
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
@ -122,8 +124,6 @@
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramNotifyTime"}}' desc='{{ i18n "pages.setting.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.setting.telegramNotifyTime"}}' desc='{{ i18n "pages.setting.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.tgNotifyBackup" }}' desc='{{ i18n "pages.setting.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.setting.tgNotifyBackup" }}' desc='{{ i18n "pages.setting.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyExpireTimeDiff" }}' desc='{{ i18n "pages.setting.tgNotifyExpireTimeDiffDesc" }}' v-model="allSetting.tgExpireDiff" :min="0"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyTrafficDiff" }}' desc='{{ i18n "pages.setting.tgNotifyTrafficDiffDesc" }}' v-model="allSetting.tgTrafficDiff" :min="0"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
</a-list> </a-list>
</a-tab-pane> </a-tab-pane>

View file

@ -394,11 +394,16 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
if len(traffics) == 0 { if len(traffics) == 0 {
return nil return nil
} }
db := database.GetDB()
dbInbound := db.Model(model.Inbound{})
traffics, err = s.adjustTraffics(traffics)
if err != nil {
return err
}
db := database.GetDB()
db = db.Model(xray.ClientTraffic{}) db = db.Model(xray.ClientTraffic{})
tx := db.Begin() tx := db.Begin()
defer func() { defer func() {
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@ -406,7 +411,20 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
tx.Commit() tx.Commit()
} }
}() }()
err = tx.Save(traffics).Error
if err != nil {
logger.Warning("AddClientTraffic update data ", err)
}
return nil
}
func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_traffics []*xray.ClientTraffic, err error) {
db := database.GetDB()
dbInbound := db.Model(model.Inbound{})
txInbound := dbInbound.Begin() txInbound := dbInbound.Begin()
defer func() { defer func() {
if err != nil { if err != nil {
txInbound.Rollback() txInbound.Rollback()
@ -415,52 +433,68 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
} }
}() }()
for _, traffic := range traffics { for traffic_index, traffic := range traffics {
inbound := &model.Inbound{} inbound := &model.Inbound{}
client := &xray.ClientTraffic{} client_traffic := &xray.ClientTraffic{}
err := tx.Where("email = ?", traffic.Email).First(client).Error err := db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(client_traffic).Error
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
logger.Warning(err, traffic.Email) logger.Warning(err, traffic.Email)
} }
continue continue
} }
client_traffic.Up += traffic.Up
client_traffic.Down += traffic.Down
err = txInbound.Where("id=?", client.InboundId).First(inbound).Error err = txInbound.Where("id=?", client_traffic.InboundId).First(inbound).Error
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
logger.Warning(err, traffic.Email) logger.Warning(err, traffic.Email)
} }
continue continue
} }
// get settings clients // get clients
settings := map[string][]model.Client{} clients, err := s.getClients(inbound)
json.Unmarshal([]byte(inbound.Settings), &settings) needUpdate := false
clients := settings["clients"] if err == nil {
for _, client := range clients { for client_index, client := range clients {
if traffic.Email == client.Email { if traffic.Email == client.Email {
traffic.ExpiryTime = client.ExpiryTime if client.ExpiryTime < 0 {
traffic.Total = client.TotalGB clients[client_index].ExpiryTime = (time.Now().Unix() * 1000) - client.ExpiryTime
needUpdate = true
}
client_traffic.ExpiryTime = client.ExpiryTime
client_traffic.Total = client.TotalGB
break
}
} }
} }
if tx.Where("inbound_id = ? and email = ?", inbound.Id, traffic.Email).
UpdateColumns(map[string]interface{}{ if needUpdate {
"enable": true, settings := map[string]interface{}{}
"expiry_time": traffic.ExpiryTime, json.Unmarshal([]byte(inbound.Settings), &settings)
"total": traffic.Total,
"up": gorm.Expr("up + ?", traffic.Up), // Convert clients to []interface to update clients in settings
"down": gorm.Expr("down + ?", traffic.Down)}).RowsAffected == 0 { var clientsInterface []interface{}
err = tx.Create(traffic).Error for _, c := range clients {
} clientsInterface = append(clientsInterface, interface{}(c))
}
if err != nil {
logger.Warning("AddClientTraffic update data ", err) settings["clients"] = clientsInterface
continue modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return nil, err
}
err = txInbound.Where("id=?", inbound.Id).Update("settings", string(modifiedSettings)).Error
if err != nil {
return nil, err
}
} }
traffics[traffic_index] = client_traffic
} }
return return traffics, nil
} }
func (s *InboundService) DisableInvalidInbounds() (int64, error) { func (s *InboundService) DisableInvalidInbounds() (int64, error) {
@ -545,11 +579,58 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
} }
return nil return nil
} }
func (s *InboundService) GetClientTrafficTgBot(tguname string) (traffic []*xray.ClientTraffic, err error) {
db := database.GetDB()
var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email like ?", "%@"+tguname).Find(&traffics).Error func (s *InboundService) ResetAllClientTraffics(id int) error {
db := database.GetDB()
result := db.Model(xray.ClientTraffic{}).
Where("inbound_id = ?", id).
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
err := result.Error
if err != nil {
return err
}
return nil
}
func (s *InboundService) ResetAllTraffics() error {
db := database.GetDB()
result := db.Model(model.Inbound{}).
Where("user_id > ?", 0).
Updates(map[string]interface{}{"up": 0, "down": 0})
err := result.Error
if err != nil {
return err
}
return nil
}
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
db := database.GetDB()
var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": "%s"%%`, tguname)).Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
var emails []string
for _, inbound := range inbounds {
clients, err := s.getClients(inbound)
if err != nil {
logger.Error("Unable to get clients from inbound")
}
for _, client := range clients {
if client.TgID == tguname {
emails = append(emails, client.Email)
}
}
}
var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
logger.Warning(err) logger.Warning(err)

View file

@ -13,6 +13,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"time" "time"
"x-ui/config"
"x-ui/logger" "x-ui/logger"
"x-ui/util/sys" "x-ui/util/sys"
"x-ui/xray" "x-ui/xray"
@ -327,11 +328,11 @@ func (s *ServerService) UpdateXray(version string) error {
} }
func (s *ServerService) GetLogs() ([]string, error) { func (s *ServerService) GetLogs(count string) ([]string, error) {
// Define the journalctl command and its arguments // Define the journalctl command and its arguments
var cmdArgs []string var cmdArgs []string
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", "100"} cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count}
} else { } else {
return []string{"Unsupported operating system"}, nil return []string{"Unsupported operating system"}, nil
} }
@ -349,3 +350,43 @@ func (s *ServerService) GetLogs() ([]string, error) {
return lines, nil return lines, nil
} }
func (s *ServerService) GetConfigJson() (interface{}, error) {
// Open the file for reading
file, err := os.Open(xray.GetConfigPath())
if err != nil {
return nil, err
}
defer file.Close()
// Read the file contents
fileContents, err := io.ReadAll(file)
if err != nil {
return nil, err
}
var jsonData interface{}
err = json.Unmarshal(fileContents, &jsonData)
if err != nil {
return nil, err
}
return jsonData, nil
}
func (s *ServerService) GetDb() ([]byte, error) {
// Open the file for reading
file, err := os.Open(config.GetDBPath())
if err != nil {
return nil, err
}
defer file.Close()
// Read the file contents
fileContents, err := io.ReadAll(file)
if err != nil {
return nil, err
}
return fileContents, nil
}

View file

@ -28,14 +28,14 @@ var defaultValueMap = map[string]string{
"webKeyFile": "", "webKeyFile": "",
"secret": random.Seq(32), "secret": random.Seq(32),
"webBasePath": "/", "webBasePath": "/",
"expireDiff": "0",
"trafficDiff": "0",
"timeLocation": "Asia/Tehran", "timeLocation": "Asia/Tehran",
"tgBotEnable": "false", "tgBotEnable": "false",
"tgBotToken": "", "tgBotToken": "",
"tgBotChatId": "", "tgBotChatId": "",
"tgRunTime": "@daily", "tgRunTime": "@daily",
"tgBotBackup": "false", "tgBotBackup": "false",
"tgExpireDiff": "0",
"tgTrafficDiff": "0",
"tgCpu": "0", "tgCpu": "0",
} }
@ -238,22 +238,6 @@ func (s *SettingService) SetTgBotBackup(value bool) error {
return s.setBool("tgBotBackup", value) return s.setBool("tgBotBackup", value)
} }
func (s *SettingService) GetTgExpireDiff() (int, error) {
return s.getInt("tgExpireDiff")
}
func (s *SettingService) SetTgExpireDiff(value int) error {
return s.setInt("tgExpireDiff", value)
}
func (s *SettingService) GetTgTrafficDiff() (int, error) {
return s.getInt("tgTrafficDiff")
}
func (s *SettingService) SetTgTrafficDiff(value int) error {
return s.setInt("tgTrafficDiff", value)
}
func (s *SettingService) GetTgCpu() (int, error) { func (s *SettingService) GetTgCpu() (int, error) {
return s.getInt("tgCpu") return s.getInt("tgCpu")
} }
@ -278,6 +262,22 @@ func (s *SettingService) GetKeyFile() (string, error) {
return s.getString("webKeyFile") return s.getString("webKeyFile")
} }
func (s *SettingService) GetExpireDiff() (int, error) {
return s.getInt("expireDiff")
}
func (s *SettingService) SetExpireDiff(value int) error {
return s.setInt("expireDiff", value)
}
func (s *SettingService) GetTrafficDiff() (int, error) {
return s.getInt("trafficDiff")
}
func (s *SettingService) SetgetTrafficDiff(value int) error {
return s.setInt("trafficDiff", value)
}
func (s *SettingService) GetSecret() ([]byte, error) { func (s *SettingService) GetSecret() ([]byte, error) {
secret, err := s.getString("secret") secret, err := s.getString("secret")
if secret == defaultValueMap["secret"] { if secret == defaultValueMap["secret"] {

555
web/service/sub.go Normal file
View file

@ -0,0 +1,555 @@
package service
import (
"encoding/base64"
"fmt"
"net/url"
"strings"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"github.com/goccy/go-json"
"gorm.io/gorm"
)
type SubService struct {
address string
inboundService InboundService
}
func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
s.address = host
var result []string
inbounds, err := s.getInboundsBySubId(subId)
if err != nil {
return nil, err
}
for _, inbound := range inbounds {
clients, err := s.inboundService.getClients(inbound)
if err != nil {
logger.Error("SubService - GetSub: Unable to get clients from inbound")
}
if clients == nil {
continue
}
for _, client := range clients {
if client.SubID == subId {
link := s.getLink(inbound, client.Email)
result = append(result, link)
}
}
}
return result, nil
}
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
db := database.GetDB()
var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
return inbounds, nil
}
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
switch inbound.Protocol {
case "vmess":
return s.genVmessLink(inbound, email)
case "vless":
return s.genVlessLink(inbound, email)
case "trojan":
return s.genTrojanLink(inbound, email)
}
return ""
}
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
address := s.address
if inbound.Protocol != model.VMess {
return ""
}
var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
network, _ := stream["network"].(string)
typeStr := "none"
host := ""
path := ""
sni := ""
fp := ""
var alpn []string
allowInsecure := false
switch network {
case "tcp":
tcp, _ := stream["tcpSettings"].(map[string]interface{})
header, _ := tcp["header"].(map[string]interface{})
typeStr, _ = header["type"].(string)
if typeStr == "http" {
request := header["request"].(map[string]interface{})
requestPath, _ := request["path"].([]interface{})
path = requestPath[0].(string)
headers, _ := request["headers"].(map[string]interface{})
host = searchHost(headers)
}
case "kcp":
kcp, _ := stream["kcpSettings"].(map[string]interface{})
header, _ := kcp["header"].(map[string]interface{})
typeStr, _ = header["type"].(string)
path, _ = kcp["seed"].(string)
case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{})
path = ws["path"].(string)
headers, _ := ws["headers"].(map[string]interface{})
host = searchHost(headers)
case "http":
network = "h2"
http, _ := stream["httpSettings"].(map[string]interface{})
path, _ = http["path"].(string)
host = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
header := quic["header"].(map[string]interface{})
typeStr, _ = header["type"].(string)
host, _ = quic["security"].(string)
path, _ = quic["key"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
path = grpc["serviceName"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
sni, _ = sniValue.(string)
}
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
fp, _ = fpValue.(string)
}
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
allowInsecure, _ = insecure.(bool)
}
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
}
clients, _ := s.inboundService.getClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
clientIndex = i
break
}
}
obj := map[string]interface{}{
"v": "2",
"ps": email,
"add": address,
"port": inbound.Port,
"id": clients[clientIndex].ID,
"aid": clients[clientIndex].AlterIds,
"net": network,
"type": typeStr,
"host": host,
"path": path,
"tls": security,
"sni": sni,
"fp": fp,
"alpn": strings.Join(alpn, ","),
"allowInsecure": allowInsecure,
}
jsonStr, _ := json.MarshalIndent(obj, "", " ")
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
}
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
address := s.address
if inbound.Protocol != model.VLESS {
return ""
}
var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.getClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
clientIndex = i
break
}
}
uuid := clients[clientIndex].ID
port := inbound.Port
streamNetwork := stream["network"].(string)
params := make(map[string]string)
params["type"] = streamNetwork
switch streamNetwork {
case "tcp":
tcp, _ := stream["tcpSettings"].(map[string]interface{})
header, _ := tcp["header"].(map[string]interface{})
typeStr, _ := header["type"].(string)
if typeStr == "http" {
request := header["request"].(map[string]interface{})
requestPath, _ := request["path"].([]interface{})
params["path"] = requestPath[0].(string)
headers, _ := request["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
params["headerType"] = "http"
}
case "kcp":
kcp, _ := stream["kcpSettings"].(map[string]interface{})
header, _ := kcp["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
params["seed"] = kcp["seed"].(string)
case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{})
params["path"] = ws["path"].(string)
headers, _ := ws["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
case "http":
http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string)
params["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
params["quicSecurity"] = quic["security"].(string)
params["key"] = quic["key"].(string)
header := quic["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
if insecure.(bool) {
params["allowInsecure"] = "1"
}
}
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
}
if security == "reality" {
params["security"] = "reality"
realitySettings, _ := stream["realitySettings"].(map[string]interface{})
if realitySettings != nil {
if sniValue, ok := searchKey(realitySettings, "serverNames"); ok {
sNames, _ := sniValue.([]interface{})
params["sni"], _ = sNames[0].(string)
}
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string)
}
if sidValue, ok := searchKey(realitySettings, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{})
params["sid"], _ = shortIds[0].(string)
}
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
}
if security == "xtls" {
params["security"] = "xtls"
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{})
alpns, _ := xtlsSetting["alpn"].([]interface{})
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
XTLSSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil {
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok {
if insecure.(bool) {
params["allowInsecure"] = "1"
}
}
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
serverName, _ := xtlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
}
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
q.Add(k, v)
}
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = email
return url.String()
}
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
address := s.address
if inbound.Protocol != model.Trojan {
return ""
}
var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.getClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
clientIndex = i
break
}
}
password := clients[clientIndex].Password
port := inbound.Port
streamNetwork := stream["network"].(string)
params := make(map[string]string)
params["type"] = streamNetwork
switch streamNetwork {
case "tcp":
tcp, _ := stream["tcpSettings"].(map[string]interface{})
header, _ := tcp["header"].(map[string]interface{})
typeStr, _ := header["type"].(string)
if typeStr == "http" {
request := header["request"].(map[string]interface{})
requestPath, _ := request["path"].([]interface{})
params["path"] = requestPath[0].(string)
headers, _ := request["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
params["headerType"] = "http"
}
case "kcp":
kcp, _ := stream["kcpSettings"].(map[string]interface{})
header, _ := kcp["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
params["seed"] = kcp["seed"].(string)
case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{})
params["path"] = ws["path"].(string)
headers, _ := ws["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
case "http":
http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string)
params["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
params["quicSecurity"] = quic["security"].(string)
params["key"] = quic["key"].(string)
header := quic["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
if insecure.(bool) {
params["allowInsecure"] = "1"
}
}
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
}
if security == "reality" {
params["security"] = "reality"
realitySettings, _ := stream["realitySettings"].(map[string]interface{})
if realitySettings != nil {
if sniValue, ok := searchKey(realitySettings, "serverNames"); ok {
sNames, _ := sniValue.([]interface{})
params["sni"], _ = sNames[0].(string)
}
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string)
}
if sidValue, ok := searchKey(realitySettings, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{})
params["sid"], _ = shortIds[0].(string)
}
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
}
if security == "xtls" {
params["security"] = "xtls"
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{})
alpns, _ := xtlsSetting["alpn"].([]interface{})
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
XTLSSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil {
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok {
if insecure.(bool) {
params["allowInsecure"] = "1"
}
}
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
serverName, _ := xtlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
}
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
q.Add(k, v)
}
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = email
return url.String()
}
func searchKey(data interface{}, key string) (interface{}, bool) {
switch val := data.(type) {
case map[string]interface{}:
for k, v := range val {
if k == key {
return v, true
}
if result, ok := searchKey(v, key); ok {
return result, true
}
}
case []interface{}:
for _, v := range val {
if result, ok := searchKey(v, key); ok {
return result, true
}
}
}
return nil, false
}
func searchHost(headers interface{}) string {
data, _ := headers.(map[string]interface{})
for k, v := range data {
if strings.EqualFold(k, "host") {
switch v.(type) {
case []interface{}:
hosts, _ := v.([]interface{})
return hosts[0].(string)
case interface{}:
return v.(string)
}
}
}
return ""
}

View file

@ -160,14 +160,14 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
t.SendMsgToTgbot(callbackQuery.From.ID, t.getServerUsage()) t.SendMsgToTgbot(callbackQuery.From.ID, t.getServerUsage())
case "inbounds": case "inbounds":
t.SendMsgToTgbot(callbackQuery.From.ID, t.getInboundUsages()) t.SendMsgToTgbot(callbackQuery.From.ID, t.getInboundUsages())
case "exhausted_soon": case "deplete_soon":
t.SendMsgToTgbot(callbackQuery.From.ID, t.getExhausted()) t.SendMsgToTgbot(callbackQuery.From.ID, t.getExhausted())
case "get_backup": case "get_backup":
t.sendBackup(callbackQuery.From.ID) t.sendBackup(callbackQuery.From.ID)
case "client_traffic": case "client_traffic":
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName) t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
case "client_commands": case "client_commands":
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess and vless and Password for Trojan.") t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</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>")
} }
@ -190,7 +190,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
), ),
tgbotapi.NewInlineKeyboardRow( tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("Get Inbounds", "inbounds"), tgbotapi.NewInlineKeyboardButtonData("Get Inbounds", "inbounds"),
tgbotapi.NewInlineKeyboardButtonData("Exhausted soon", "exhausted_soon"), tgbotapi.NewInlineKeyboardButtonData("Deplete soon", "deplete_soon"),
), ),
tgbotapi.NewInlineKeyboardRow( tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("Commands", "commands"), tgbotapi.NewInlineKeyboardButtonData("Commands", "commands"),
@ -363,6 +363,11 @@ func (t *Tgbot) getInboundUsages() string {
} }
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) { func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
if len(tgUserName) == 0 {
msg := "Your configuration is not found!\nYou should configure your telegram username and ask Admin to add it to your configuration."
t.SendMsgToTgbot(chatId, msg)
return
}
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName) traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
if err != nil { if err != nil {
logger.Warning(err) logger.Warning(err)
@ -373,11 +378,14 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
if len(traffics) == 0 { if len(traffics) == 0 {
msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram username in your configuration(s).\n\nYour username: <b>@" + tgUserName + "</b>" msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram username in your configuration(s).\n\nYour username: <b>@" + tgUserName + "</b>"
t.SendMsgToTgbot(chatId, msg) t.SendMsgToTgbot(chatId, msg)
return
} }
for _, traffic := range traffics { for _, traffic := range traffics {
expiryTime := "" expiryTime := ""
if traffic.ExpiryTime == 0 { if traffic.ExpiryTime == 0 {
expiryTime = "♾Unlimited" expiryTime = "♾Unlimited"
} else if traffic.ExpiryTime < 0 {
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
} else { } else {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
} }
@ -412,6 +420,8 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
expiryTime := "" expiryTime := ""
if traffic.ExpiryTime == 0 { if traffic.ExpiryTime == 0 {
expiryTime = "♾Unlimited" expiryTime = "♾Unlimited"
} else if traffic.ExpiryTime < 0 {
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
} else { } else {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
} }
@ -450,6 +460,8 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
expiryTime := "" expiryTime := ""
if traffic.ExpiryTime == 0 { if traffic.ExpiryTime == 0 {
expiryTime = "♾Unlimited" expiryTime = "♾Unlimited"
} else if traffic.ExpiryTime < 0 {
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
} else { } else {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
} }
@ -483,6 +495,8 @@ func (t *Tgbot) searchForClient(chatId int64, query string) {
expiryTime := "" expiryTime := ""
if traffic.ExpiryTime == 0 { if traffic.ExpiryTime == 0 {
expiryTime = "♾Unlimited" expiryTime = "♾Unlimited"
} else if traffic.ExpiryTime < 0 {
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
} else { } else {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
} }
@ -507,13 +521,13 @@ func (t *Tgbot) getExhausted() string {
var disabledInbounds []model.Inbound var disabledInbounds []model.Inbound
var disabledClients []xray.ClientTraffic var disabledClients []xray.ClientTraffic
output := "" output := ""
TrafficThreshold, err := t.settingService.GetTgTrafficDiff() TrafficThreshold, err := t.settingService.GetTrafficDiff()
if err == nil && TrafficThreshold > 0 { if err == nil && TrafficThreshold > 0 {
trDiff = int64(TrafficThreshold) * 1073741824 trDiff = int64(TrafficThreshold) * 1073741824
} }
ExpireThreshold, err := t.settingService.GetTgExpireDiff() ExpireThreshold, err := t.settingService.GetExpireDiff()
if err == nil && ExpireThreshold > 0 { if err == nil && ExpireThreshold > 0 {
exDiff = int64(ExpireThreshold) * 84600000 exDiff = int64(ExpireThreshold) * 86400000
} }
inbounds, err := t.inboundService.GetAllInbounds() inbounds, err := t.inboundService.GetAllInbounds()
if err != nil { if err != nil {
@ -522,14 +536,14 @@ func (t *Tgbot) getExhausted() string {
for _, inbound := range inbounds { for _, inbound := range inbounds {
if inbound.Enable { if inbound.Enable {
if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) || if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) ||
(inbound.Total > 0 && (inbound.Total-inbound.Up+inbound.Down < trDiff)) { (inbound.Total > 0 && (inbound.Total-(inbound.Up+inbound.Down) < trDiff)) {
exhaustedInbounds = append(exhaustedInbounds, *inbound) exhaustedInbounds = append(exhaustedInbounds, *inbound)
} }
if len(inbound.ClientStats) > 0 { if len(inbound.ClientStats) > 0 {
for _, client := range inbound.ClientStats { for _, client := range inbound.ClientStats {
if client.Enable { if client.Enable {
if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) || if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) ||
(client.Total > 0 && (client.Total-client.Up+client.Down < trDiff)) { (client.Total > 0 && (client.Total-(client.Up+client.Down) < trDiff)) {
exhaustedClients = append(exhaustedClients, client) exhaustedClients = append(exhaustedClients, client)
} }
} else { } else {
@ -541,7 +555,7 @@ func (t *Tgbot) getExhausted() string {
disabledInbounds = append(disabledInbounds, *inbound) disabledInbounds = append(disabledInbounds, *inbound)
} }
} }
output += fmt.Sprintf("Exhausted Inbounds count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledInbounds), len(exhaustedInbounds)) output += fmt.Sprintf("Exhausted Inbounds count:\r\n🛑 Disabled: %d\r\n🔜 Deplete soon: %d\r\n \r\n", len(disabledInbounds), len(exhaustedInbounds))
if len(exhaustedInbounds) > 0 { if len(exhaustedInbounds) > 0 {
output += "Exhausted Inbounds:\r\n" output += "Exhausted Inbounds:\r\n"
for _, inbound := range exhaustedInbounds { for _, inbound := range exhaustedInbounds {
@ -553,13 +567,15 @@ func (t *Tgbot) getExhausted() string {
} }
} }
} }
output += fmt.Sprintf("Exhausted Clients count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledClients), len(exhaustedClients)) output += fmt.Sprintf("Exhausted Clients count:\r\n🛑 Exhausted: %d\r\n🔜 Deplete soon: %d\r\n \r\n", len(disabledClients), len(exhaustedClients))
if len(exhaustedClients) > 0 { if len(exhaustedClients) > 0 {
output += "Exhausted Clients:\r\n" output += "Exhausted Clients:\r\n"
for _, traffic := range exhaustedClients { for _, traffic := range exhaustedClients {
expiryTime := "" expiryTime := ""
if traffic.ExpiryTime == 0 { if traffic.ExpiryTime == 0 {
expiryTime = "♾Unlimited" expiryTime = "♾Unlimited"
} else if traffic.ExpiryTime < 0 {
expiryTime += fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
} else { } else {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
} }

View file

@ -84,15 +84,16 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
clients, ok := settings["clients"].([]interface{}) clients, ok := settings["clients"].([]interface{})
if ok { if ok {
// check users active or not // check users active or not
clientStats := inbound.ClientStats clientStats := inbound.ClientStats
for _, clientTraffic := range clientStats { for _, clientTraffic := range clientStats {
indexDecrease := 0
for index, client := range clients { for index, client := range clients {
c := client.(map[string]interface{}) c := client.(map[string]interface{})
if c["email"] == clientTraffic.Email { if c["email"] == clientTraffic.Email {
if !clientTraffic.Enable { if !clientTraffic.Enable {
clients = RemoveIndex(clients, index) clients = RemoveIndex(clients, index-indexDecrease)
indexDecrease++
logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit") logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit")
} }
@ -101,7 +102,27 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
} }
} }
settings["clients"] = clients
// clear client config for additional parameters
var final_clients []interface{}
for _, client := range clients {
c := client.(map[string]interface{})
if c["enable"] != nil {
if enable, ok := c["enable"].(bool); ok && !enable {
continue
}
}
for key := range c {
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
delete(c, key)
}
}
final_clients = append(final_clients, interface{}(c))
}
settings["clients"] = final_clients
modifiedSettings, err := json.Marshal(settings) modifiedSettings, err := json.Marshal(settings)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -33,8 +33,11 @@
"host" = "Host" "host" = "Host"
"path" = "Path" "path" = "Path"
"camouflage" = "Camouflage" "camouflage" = "Camouflage"
"status" = "Status"
"enabled" = "Enabled" "enabled" = "Enabled"
"disabled" = "Disabled" "disabled" = "Disabled"
"depleted" = "Depleted"
"depletingSoon" = "Depleting soon"
"domainName" = "Domain name" "domainName" = "Domain name"
"additional" = "Alter" "additional" = "Alter"
"monitor" = "Listen IP" "monitor" = "Listen IP"
@ -140,11 +143,17 @@
"resetAllTrafficCancelText" = "Cancel" "resetAllTrafficCancelText" = "Cancel"
"IPLimit" = "IP Limit" "IPLimit" = "IP Limit"
"IPLimitDesc" = "disable inbound if more than entered count (0 for disable limit ip)" "IPLimitDesc" = "disable inbound if more than entered count (0 for disable limit ip)"
"resetAllClientTraffics" = "Reset Clients Traffic"
"resetAllClientTrafficTitle" = "Reset all clients traffic"
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
"Email" = "Email" "Email" = "Email"
"EmailDesc" = "The Email Must Be Completely Unique" "EmailDesc" = "The Email Must Be Completely Unique"
"IPLimitlog" = "IP Log" "IPLimitlog" = "IP Log"
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)" "IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
"IPLimitlogclear" = "Clear The Log" "IPLimitlogclear" = "Clear The Log"
"setDefaultCert" = "Set cert from panel"
"XTLSdec" = "Xray core needs to be 1.7.5 and below"
"Realitydec" = "Xray core needs to be 1.8.0 and above"
[pages.client] [pages.client]
"add" = "Add client" "add" = "Add client"
@ -158,6 +167,9 @@
"last" = "Last" "last" = "Last"
"prefix" = "Prefix" "prefix" = "Prefix"
"postfix" = "postfix" "postfix" = "postfix"
"delayedStart" = "Start after first use"
"expireDays" = "Expire days"
"days" = "day(s)"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Obtain" "obtain" = "Obtain"
@ -231,10 +243,10 @@
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect" "telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
"tgNotifyBackup" = "Database backup" "tgNotifyBackup" = "Database backup"
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect" "tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
"tgNotifyExpireTimeDiff" = "Remained time threshold" "expireTimeDiff" = "Exhaustion time threshold"
"tgNotifyExpireTimeDiffDesc" = "This telegram bot will send you a notification before expiration (unit:day)" "expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
"tgNotifyTrafficDiff" = "Remained traffic threshold" "trafficDiff" = "Exhaustion traffic threshold"
"tgNotifyTrafficDiffDesc" = "This telegram bot will send you a notification before finishing traffic (unit:GB)" "trafficDiffDesc" = "Detect exhaustion before finishing traffic (unit:GB)"
"tgNotifyCpu" = "CPU percentage alert threshold" "tgNotifyCpu" = "CPU percentage alert threshold"
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)" "tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
"timeZonee" = "Time Zone" "timeZonee" = "Time Zone"

View file

@ -33,8 +33,11 @@
"host" = "آدرس" "host" = "آدرس"
"path" = "مسیر" "path" = "مسیر"
"camouflage" = "استتار" "camouflage" = "استتار"
"status" = "وضعیت"
"enabled" = "فعال" "enabled" = "فعال"
"disabled" = "غیرفعال" "disabled" = "غیرفعال"
"depleted" = "منقضی"
"depletingSoon" = "در حال انقضا"
"domainName" = "آدرس دامنه" "domainName" = "آدرس دامنه"
"additional" = "آی دی جایگزین" "additional" = "آی دی جایگزین"
"monitor" = "آی پی اتصال" "monitor" = "آی پی اتصال"
@ -133,11 +136,12 @@
"cloneInbound" = "ایجاد" "cloneInbound" = "ایجاد"
"cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد" "cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد"
"cloneInboundOk" = "ساختن شبیه ساز" "cloneInboundOk" = "ساختن شبیه ساز"
"resetAllTraffic" = "ریست ترافیک کل ورودی ها" "resetAllTraffic" = "ریست ترافیک کل سرویس ها"
"resetAllTrafficTitle" = "ریست ترافیک کل ورودی ها" "resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها"
"resetAllTrafficContent" = "آیا مطمئن هستید که تمام ترافیک ورودی ها را ریست می کنید؟" "resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟"
"resetAllTrafficOkText" = "بله" "resetAllClientTraffics" = "ریست ترافیک کاربران"
"resetAllTrafficCancelText" = "انصراف" "resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
"IPLimit" = "محدودیت ای پی" "IPLimit" = "محدودیت ای پی"
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )" "IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
"Email" = "ایمیل" "Email" = "ایمیل"
@ -145,6 +149,9 @@
"IPLimitlog" = "گزارش ها" "IPLimitlog" = "گزارش ها"
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)" "IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
"IPLimitlogclear" = "پاک کردن گزارش ها" "IPLimitlogclear" = "پاک کردن گزارش ها"
"setDefaultCert" = "استفاده از گواهی پنل"
"XTLSdec" = "هسته Xray باید 1.7.5 و کمتر باشد"
"Realitydec" = "هسته Xray باید 1.8.0 و بالاتر باشد"
[pages.client] [pages.client]
"add" = "کاربر جدید" "add" = "کاربر جدید"
@ -158,6 +165,9 @@
"last" = "تا" "last" = "تا"
"prefix" = "پیشوند" "prefix" = "پیشوند"
"postfix" = "پسوند" "postfix" = "پسوند"
"delayedStart" = "شروع بعد از اولین استفاده"
"expireDays" = "روزهای اعتبار"
"days" = "(روز)"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Obtain" "obtain" = "Obtain"
@ -228,13 +238,13 @@
"telegramChatId" = "آی دی تلگرام مدیریت" "telegramChatId" = "آی دی تلگرام مدیریت"
"telegramChatIdDesc" = "با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود" "telegramChatIdDesc" = "با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام" "telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
"telegramNotifyTimeDesc" = "از فرمت زمان بندی Crontab استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود" "telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده" "tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای" "tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
"tgNotifyExpireTimeDiff" = "آستانه زمان باقی مانده" "expireTimeDiff" = "آستانه زمان باقی مانده"
"tgNotifyExpireTimeDiffDesc" = "این ربات تلگرام قبل از انقضا برای شما پیام ارسال می کند (واحد: روز)" "expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
"tgNotifyTrafficDiff" = "آستانه ترافیک باقی مانده" "trafficDiff" = "آستانه ترافیک باقی مانده"
"tgNotifyTrafficDiffDesc" = "این ربات تلگرام قبل از اتمام ترافیک برای شما پیام ارسال می کند (واحد: گیگابایت)" "trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)"
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده" "tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)" "tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
"timeZonee" = "منظقه زمانی" "timeZonee" = "منظقه زمانی"

View file

@ -33,8 +33,11 @@
"host" = "主持人" "host" = "主持人"
"path" = "小路" "path" = "小路"
"camouflage" = "伪装" "camouflage" = "伪装"
"status" = "状态"
"enabled" = "开启" "enabled" = "开启"
"disabled" = "关闭" "disabled" = "关闭"
"depleted" = "耗尽"
"depletingSoon" = "即将耗尽"
"domainName" = "域名" "domainName" = "域名"
"additional" = "额外" "additional" = "额外"
"monitor" = "监听" "monitor" = "监听"
@ -69,8 +72,8 @@
"memory" = "内存" "memory" = "内存"
"hard" = "硬盘" "hard" = "硬盘"
"xrayStatus" = "xray 状态" "xrayStatus" = "xray 状态"
"stopXray" = "停止 Xray" "stopXray" = "停止"
"restartXray" = "重启 Xray" "restartXray" = "重启"
"xraySwitch" = "切换版本" "xraySwitch" = "切换版本"
"xraySwitchClick" = "点击你想切换的版本" "xraySwitchClick" = "点击你想切换的版本"
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容" "xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
@ -136,8 +139,9 @@
"resetAllTraffic" = "重置所有入站流量" "resetAllTraffic" = "重置所有入站流量"
"resetAllTrafficTitle" = "重置所有入站流量" "resetAllTrafficTitle" = "重置所有入站流量"
"resetAllTrafficContent" = "您确定要重置所有入站流量吗?" "resetAllTrafficContent" = "您确定要重置所有入站流量吗?"
"resetAllTrafficOkText" = "确认" "resetAllClientTraffics" = "重置客户端流量"
"resetAllTrafficCancelText" = "取消" "resetAllClientTrafficTitle" = "重置所有客户端流量"
"resetAllClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
"IPLimit" = "IP限制" "IPLimit" = "IP限制"
"IPLimitDesc" = "如果超过输入的计数则禁用入站0 表示禁用限制 ip" "IPLimitDesc" = "如果超过输入的计数则禁用入站0 表示禁用限制 ip"
"Email" = "电子邮件" "Email" = "电子邮件"
@ -145,6 +149,9 @@
"IPLimitlog" = "IP日志" "IPLimitlog" = "IP日志"
"IPLimitlogDesc" = "IP 历史日志 通过IP限制禁用inbound之前需要清空日志" "IPLimitlogDesc" = "IP 历史日志 通过IP限制禁用inbound之前需要清空日志"
"IPLimitlogclear" = "清除日志" "IPLimitlogclear" = "清除日志"
"setDefaultCert" = "从面板设置证书"
"XTLSdec" = "Xray核心需要1.7.5及以下版本"
"Realitydec" = "Xray核心需要1.8.0及以上版本"
[pages.client] [pages.client]
"add" = "添加客户端" "add" = "添加客户端"
@ -158,6 +165,9 @@
"last" = "最后" "last" = "最后"
"prefix" = "前缀" "prefix" = "前缀"
"postfix" = "后缀" "postfix" = "后缀"
"delayedStart" = "首次使用后开始"
"expireDays" = "过期天数"
"days" = "天"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "获取" "obtain" = "获取"
@ -231,10 +241,10 @@
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效" "telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
"tgNotifyBackup" = "数据库备份" "tgNotifyBackup" = "数据库备份"
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效" "tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
"tgNotifyExpireTimeDiff" = "剩余时间阈值" "expireTimeDiff" = "耗尽时间阈值"
"tgNotifyExpireTimeDiffDesc" = "这个 talegram bot 会在到期前给你发送通知(单位:天)" "expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
"tgNotifyTrafficDiff" = "剩余流量阈值" "trafficDiff" = "耗尽流量阈值"
"tgNotifyTrafficDiffDesc" = "这个 talegram bot 会在流量结束前给你发送通知单位GB" "trafficDiffDesc" = "完成流量前检测耗尽单位GB"
"tgNotifyCpu" = "CPU 百分比警报阈值" "tgNotifyCpu" = "CPU 百分比警报阈值"
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知" "tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
"timeZonee" = "时区" "timeZonee" = "时区"

View file

@ -33,6 +33,9 @@ import (
//go:embed assets/* //go:embed assets/*
var assetsFS embed.FS var assetsFS embed.FS
//go:embed assets/favicon.ico
var favicon []byte
//go:embed html/* //go:embed html/*
var htmlFS embed.FS var htmlFS embed.FS
@ -85,6 +88,7 @@ type Server struct {
server *controller.ServerController server *controller.ServerController
xui *controller.XUIController xui *controller.XUIController
api *controller.APIController api *controller.APIController
sub *controller.SUBController
xrayService service.XrayService xrayService service.XrayService
settingService service.SettingService settingService service.SettingService
@ -158,7 +162,9 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine := gin.Default() engine := gin.Default()
// Add favicon // Add favicon
engine.StaticFile("/favicon.ico", "web/assets/favicon.ico") engine.GET("/favicon.ico", func(c *gin.Context) {
c.Data(200, "image/x-icon", favicon)
})
secret, err := s.settingService.GetSecret() secret, err := s.settingService.GetSecret()
if err != nil { if err != nil {
@ -211,6 +217,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
s.server = controller.NewServerController(g) s.server = controller.NewServerController(g)
s.xui = controller.NewXUIController(g) s.xui = controller.NewXUIController(g)
s.api = controller.NewAPIController(g) s.api = controller.NewAPIController(g)
s.sub = controller.NewSUBController(g)
return engine, nil return engine, nil
} }

View file

@ -455,6 +455,14 @@ ssl_cert_issue() {
} }
open_ports() { open_ports() {
if ! command -v ufw &> /dev/null
then
echo "ufw firewall is not installed. Installing now..."
sudo apt-get update
sudo apt-get install -y ufw
else
echo "ufw firewall is already installed"
fi
# Check if the firewall is inactive # Check if the firewall is inactive
if sudo ufw status | grep -q "Status: active"; then if sudo ufw status | grep -q "Status: active"; then