Merge branch 'MHSanaei:main' into main
30
README.md
|
@ -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+
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1.1.4
|
1.2.2
|
||||||
|
|
|
@ -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
|
@ -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
|
@ -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=
|
||||||
|
|
32
install.sh
|
@ -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}"
|
||||||
|
|
BIN
media/1.png
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
BIN
media/2.png
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 63 KiB |
BIN
media/3.png
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 59 KiB |
BIN
media/4.png
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 157 KiB |
|
@ -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;
|
||||||
}
|
}
|
|
@ -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 = "";
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 = '';
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
@ -0,0 +1,42 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SUBController struct {
|
||||||
|
BaseController
|
||||||
|
|
||||||
|
subService service.SubService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
||||||
|
a := &SUBController{}
|
||||||
|
a.initRouter(g)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||||
|
g = g.Group("/sub")
|
||||||
|
|
||||||
|
g.GET("/:subid", a.subs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
|
subId := c.Param("subid")
|
||||||
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
|
subs, err := a.subService.GetSubs(subId, host)
|
||||||
|
if err != nil {
|
||||||
|
c.String(400, "Error!")
|
||||||
|
} else {
|
||||||
|
result := ""
|
||||||
|
for _, sub := range subs {
|
||||||
|
result += sub + "\n"
|
||||||
|
}
|
||||||
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,13 +32,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"`
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'">
|
||||||
|
|
|
@ -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}}
|
|
@ -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}}
|
|
@ -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) {
|
||||||
|
|
|
@ -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 = '';
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
@ -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 ""
|
||||||
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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" = "منظقه زمانی"
|
||||||
|
|
|
@ -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" = "时区"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
8
x-ui.sh
|
@ -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
|
||||||
|
|