mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-14 05:23:09 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6199526da | ||
|
|
da5253d98c | ||
|
|
7adc8755f8 | ||
|
|
af5d681c22 | ||
|
|
28a3fc813c | ||
|
|
55ae60594f | ||
|
|
7cb99d47e2 | ||
|
|
c3970a4978 | ||
|
|
5daec0cf9e | ||
|
|
1b0de200c0 | ||
|
|
2c53d987eb | ||
|
|
fc725a56c3 | ||
|
|
01028530c2 | ||
|
|
d986ec5c3c | ||
|
|
812e145f97 | ||
|
|
5261a884bf | ||
|
|
c6816d2531 | ||
|
|
19073469c5 | ||
|
|
c3b1f6a13d | ||
|
|
11b758743a | ||
|
|
a92e3b598f | ||
|
|
413fa468d1 | ||
|
|
db32b581db | ||
|
|
078b408e4f | ||
|
|
b04a892596 | ||
|
|
3304daff7e | ||
|
|
9e3c7b1db6 | ||
|
|
ab6c6c0ca6 | ||
|
|
9c0890dd9b | ||
|
|
eee0503200 | ||
|
|
f3c539dd73 | ||
|
|
c0464f1d97 | ||
|
|
b87474d70c | ||
|
|
063309dbb7 | ||
|
|
294b680972 | ||
|
|
f55478422b | ||
|
|
619e0c69cd | ||
|
|
da5dc3a04f |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 'stable'
|
||||
go-version: '1.20'
|
||||
- name: build linux amd64 version
|
||||
run: |
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
wget https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
mv xray xray-linux-amd64
|
||||
cd ..
|
||||
cd ..
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
wget https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
mv xray xray-linux-arm64
|
||||
cd ..
|
||||
cd ..
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
wget https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
mv xray xray-linux-s390x
|
||||
cd ..
|
||||
cd ..
|
||||
|
||||
@@ -17,5 +17,5 @@ rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
wget "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat"
|
||||
cd ../../
|
||||
wget "https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.dat"
|
||||
cd ../../
|
||||
|
||||
19
README.md
19
README.md
@@ -217,20 +217,6 @@ Reference syntax:
|
||||
- Multi language bot
|
||||
</details>
|
||||
|
||||
# Common problem
|
||||
|
||||
<details>
|
||||
<summary>Click for details</summary>
|
||||
## Migrating from v2-ui
|
||||
|
||||
First install the latest version of x-ui on the server where v2-ui is installed, and then use the following command to migrate, which will migrate the native v2-ui `All inbound account data` to x-ui,`Panel settings and username passwords are not migrated`
|
||||
|
||||
> Please `Close v2-ui` and `restart x-ui`, otherwise the inbound of v2-ui will cause a `port conflict with the inbound of x-ui`
|
||||
|
||||
```sh
|
||||
x-ui v2-ui
|
||||
```
|
||||
|
||||
# T-Shoots:
|
||||
|
||||
**If you upgrade from an old version or other forks, for enable traffic for users you should do :**
|
||||
@@ -281,6 +267,11 @@ restart panel
|
||||
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
||||
- [MHSanaei](https://github.com/MHSanaei)
|
||||
|
||||
# Acknowledgment
|
||||
|
||||
- [Iran Hosted Domains](https://github.com/bootmortis/iran-hosted-domains) (License: **MIT**): _A comprehensive list of Iranian domains and services that are hosted within the country._
|
||||
- [PersianBlocker](https://github.com/MasterKia/PersianBlocker) (License: **AGPLv3**): _An optimal and extensive list to block ads and trackers on Persian websites._
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starchart.cc/alireza0/x-ui)
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.5.2
|
||||
1.5.4
|
||||
10
go.mod
10
go.mod
@@ -12,13 +12,13 @@ require (
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.0.9
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.6
|
||||
github.com/shirou/gopsutil/v3 v3.23.7
|
||||
github.com/xtls/xray-core v1.8.3
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.11.0
|
||||
golang.org/x/text v0.12.0
|
||||
google.golang.org/grpc v1.57.0
|
||||
gorm.io/driver/sqlite v1.5.2
|
||||
gorm.io/gorm v1.25.2
|
||||
gorm.io/driver/sqlite v1.5.3
|
||||
gorm.io/gorm v1.25.4
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -80,7 +80,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
|
||||
20
go.sum
20
go.sum
@@ -220,8 +220,8 @@ github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:eu
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08=
|
||||
github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
@@ -366,8 +366,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -376,8 +376,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
@@ -433,10 +433,10 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
||||
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
||||
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
|
||||
@@ -113,7 +113,6 @@ config_after_install() {
|
||||
}
|
||||
|
||||
install_x-ui() {
|
||||
systemctl stop x-ui
|
||||
cd /usr/local/
|
||||
|
||||
if [ $# == 0 ]; then
|
||||
@@ -140,6 +139,7 @@ install_x-ui() {
|
||||
fi
|
||||
|
||||
if [[ -e /usr/local/x-ui/ ]]; then
|
||||
systemctl stop x-ui
|
||||
rm /usr/local/x-ui/ -rf
|
||||
fi
|
||||
|
||||
@@ -173,7 +173,6 @@ install_x-ui() {
|
||||
echo -e "x-ui enable - Enable x-ui on system startup"
|
||||
echo -e "x-ui disable - Disable x-ui on system startup"
|
||||
echo -e "x-ui log - Check x-ui logs"
|
||||
echo -e "x-ui v2-ui - Migrate v2-ui Account data to x-ui"
|
||||
echo -e "x-ui update - Update x-ui"
|
||||
echo -e "x-ui install - Install x-ui"
|
||||
echo -e "x-ui uninstall - Uninstall x-ui"
|
||||
|
||||
20
main.go
20
main.go
@@ -12,7 +12,6 @@ import (
|
||||
"x-ui/database"
|
||||
"x-ui/logger"
|
||||
"x-ui/sub"
|
||||
"x-ui/v2ui"
|
||||
"x-ui/web"
|
||||
"x-ui/web/global"
|
||||
"x-ui/web/service"
|
||||
@@ -252,10 +251,6 @@ func main() {
|
||||
|
||||
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
||||
|
||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
||||
var dbPath string
|
||||
v2uiCmd.StringVar(&dbPath, "db", fmt.Sprintf("%s/v2-ui.db", config.GetDBFolderPath()), "set v2-ui db file path")
|
||||
|
||||
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||
var port int
|
||||
var username string
|
||||
@@ -282,7 +277,6 @@ func main() {
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" run run web panel")
|
||||
fmt.Println(" v2-ui migrate form v2-ui")
|
||||
fmt.Println(" migrate migrate form other/old x-ui")
|
||||
fmt.Println(" setting set settings")
|
||||
}
|
||||
@@ -303,16 +297,6 @@ func main() {
|
||||
runWebServer()
|
||||
case "migrate":
|
||||
migrateDb()
|
||||
case "v2-ui":
|
||||
err := v2uiCmd.Parse(os.Args[2:])
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = v2ui.MigrateFromV2UI(dbPath)
|
||||
if err != nil {
|
||||
fmt.Println("migrate from v2-ui failed:", err)
|
||||
}
|
||||
case "setting":
|
||||
err := settingCmd.Parse(os.Args[2:])
|
||||
if err != nil {
|
||||
@@ -334,12 +318,10 @@ func main() {
|
||||
updateTgbotEnableSts(enabletgbot)
|
||||
}
|
||||
default:
|
||||
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
||||
fmt.Println("except 'run' or 'setting' subcommands")
|
||||
fmt.Println()
|
||||
runCmd.Usage()
|
||||
fmt.Println()
|
||||
v2uiCmd.Usage()
|
||||
fmt.Println()
|
||||
settingCmd.Usage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||
|
||||
func (a *SUBController) subs(c *gin.Context) {
|
||||
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
||||
subShowInfo, _ := a.settingService.GetSubShowInfo()
|
||||
subId := c.Param("subid")
|
||||
host := strings.Split(c.Request.Host, ":")[0]
|
||||
subs, headers, err := a.subService.GetSubs(subId, host)
|
||||
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
|
||||
if err != nil || len(subs) == 0 {
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
|
||||
@@ -16,12 +18,14 @@ import (
|
||||
|
||||
type SubService struct {
|
||||
address string
|
||||
showInfo bool
|
||||
inboundService service.InboundService
|
||||
settingServics service.SettingService
|
||||
}
|
||||
|
||||
func (s *SubService) GetSubs(subId string, host string) ([]string, []string, error) {
|
||||
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
||||
s.address = host
|
||||
s.showInfo = showInfo
|
||||
var result []string
|
||||
var headers []string
|
||||
var traffic xray.ClientTraffic
|
||||
@@ -139,10 +143,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
if inbound.Protocol != model.VMess {
|
||||
return ""
|
||||
}
|
||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||
obj := map[string]interface{}{
|
||||
"v": "2",
|
||||
"ps": remark,
|
||||
"add": s.address,
|
||||
"port": inbound.Port,
|
||||
"type": "none",
|
||||
@@ -241,7 +243,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
links := ""
|
||||
for index, d := range domains {
|
||||
domain := d.(map[string]interface{})
|
||||
obj["ps"] = remark + "-" + domain["remark"].(string)
|
||||
obj["ps"] = s.genRemark(inbound, email, domain["remark"].(string))
|
||||
obj["add"] = domain["domain"].(string)
|
||||
if index > 0 {
|
||||
links += "\n"
|
||||
@@ -252,6 +254,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
return links
|
||||
}
|
||||
|
||||
obj["ps"] = s.genRemark(inbound, email, "")
|
||||
|
||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||
}
|
||||
@@ -407,13 +411,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
|
||||
// Set the new query values on the URL
|
||||
url.RawQuery = q.Encode()
|
||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||
|
||||
if len(domains) > 0 {
|
||||
links := ""
|
||||
for index, d := range domains {
|
||||
domain := d.(map[string]interface{})
|
||||
url.Fragment = remark + "-" + domain["remark"].(string)
|
||||
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
||||
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||
if index > 0 {
|
||||
links += "\n"
|
||||
@@ -423,7 +426,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
return links
|
||||
}
|
||||
|
||||
url.Fragment = remark
|
||||
url.Fragment = s.genRemark(inbound, email, "")
|
||||
return url.String()
|
||||
}
|
||||
|
||||
@@ -572,13 +575,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
// Set the new query values on the URL
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||
|
||||
if len(domains) > 0 {
|
||||
links := ""
|
||||
for index, d := range domains {
|
||||
domain := d.(map[string]interface{})
|
||||
url.Fragment = remark + "-" + domain["remark"].(string)
|
||||
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
||||
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||
if index > 0 {
|
||||
links += "\n"
|
||||
@@ -588,7 +589,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
return links
|
||||
}
|
||||
|
||||
url.Fragment = remark
|
||||
url.Fragment = s.genRemark(inbound, email, "")
|
||||
return url.String()
|
||||
}
|
||||
|
||||
@@ -671,12 +672,55 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
|
||||
// Set the new query values on the URL
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, clients[clientIndex].Email)
|
||||
url.Fragment = remark
|
||||
url.Fragment = s.genRemark(inbound, email, "")
|
||||
return url.String()
|
||||
}
|
||||
|
||||
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
||||
var remark []string
|
||||
if len(email) > 0 {
|
||||
if len(inbound.Remark) > 0 {
|
||||
remark = append(remark, inbound.Remark)
|
||||
}
|
||||
remark = append(remark, email)
|
||||
if len(extra) > 0 {
|
||||
remark = append(remark, extra)
|
||||
}
|
||||
} else {
|
||||
return inbound.Remark
|
||||
}
|
||||
|
||||
if s.showInfo {
|
||||
statsExist := false
|
||||
var stats xray.ClientTraffic
|
||||
for _, clientStat := range inbound.ClientStats {
|
||||
if clientStat.Email == email {
|
||||
stats = clientStat
|
||||
statsExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Get remained days
|
||||
if statsExist {
|
||||
if !stats.Enable {
|
||||
return fmt.Sprintf("⛔️N/A-%s", strings.Join(remark, "-"))
|
||||
}
|
||||
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
|
||||
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
switch exp := stats.ExpiryTime / 1000; {
|
||||
case exp > 0:
|
||||
remark = append(remark, fmt.Sprintf("%d%s⏳", (exp-now)/86400, "Days"))
|
||||
case exp < 0:
|
||||
remark = append(remark, fmt.Sprintf("%d%s⏳", exp/-86400, "Days"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(remark, "-")
|
||||
}
|
||||
|
||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||
switch val := data.(type) {
|
||||
case map[string]interface{}:
|
||||
|
||||
28
v2ui/db.go
28
v2ui/db.go
@@ -1,28 +0,0 @@
|
||||
package v2ui
|
||||
|
||||
import (
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var v2db *gorm.DB
|
||||
|
||||
func initDB(dbPath string) error {
|
||||
c := &gorm.Config{
|
||||
Logger: logger.Discard,
|
||||
}
|
||||
var err error
|
||||
v2db, err = gorm.Open(sqlite.Open(dbPath), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getV2Inbounds() ([]*V2Inbound, error) {
|
||||
inbounds := make([]*V2Inbound, 0)
|
||||
err := v2db.Model(V2Inbound{}).Find(&inbounds).Error
|
||||
return inbounds, err
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package v2ui
|
||||
|
||||
import "x-ui/database/model"
|
||||
|
||||
type V2Inbound struct {
|
||||
Id int `gorm:"primaryKey;autoIncrement"`
|
||||
Port int `gorm:"unique"`
|
||||
Listen string
|
||||
Protocol string
|
||||
Settings string
|
||||
StreamSettings string
|
||||
Tag string `gorm:"unique"`
|
||||
Sniffing string
|
||||
Remark string
|
||||
Up int64
|
||||
Down int64
|
||||
Enable bool
|
||||
}
|
||||
|
||||
func (i *V2Inbound) TableName() string {
|
||||
return "inbound"
|
||||
}
|
||||
|
||||
func (i *V2Inbound) ToInbound(userId int) *model.Inbound {
|
||||
return &model.Inbound{
|
||||
UserId: userId,
|
||||
Up: i.Up,
|
||||
Down: i.Down,
|
||||
Total: 0,
|
||||
Remark: i.Remark,
|
||||
Enable: i.Enable,
|
||||
ExpiryTime: 0,
|
||||
Listen: i.Listen,
|
||||
Port: i.Port,
|
||||
Protocol: model.Protocol(i.Protocol),
|
||||
Settings: i.Settings,
|
||||
StreamSettings: i.StreamSettings,
|
||||
Tag: i.Tag,
|
||||
Sniffing: i.Sniffing,
|
||||
}
|
||||
}
|
||||
51
v2ui/v2ui.go
51
v2ui/v2ui.go
@@ -1,51 +0,0 @@
|
||||
package v2ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"x-ui/config"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/util/common"
|
||||
"x-ui/web/service"
|
||||
)
|
||||
|
||||
func MigrateFromV2UI(dbPath string) error {
|
||||
err := initDB(dbPath)
|
||||
if err != nil {
|
||||
return common.NewError("init v2-ui database failed:", err)
|
||||
}
|
||||
err = database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
return common.NewError("init x-ui database failed:", err)
|
||||
}
|
||||
|
||||
v2Inbounds, err := getV2Inbounds()
|
||||
if err != nil {
|
||||
return common.NewError("get v2-ui inbounds failed:", err)
|
||||
}
|
||||
if len(v2Inbounds) == 0 {
|
||||
fmt.Println("migrate v2-ui inbounds success: 0")
|
||||
return nil
|
||||
}
|
||||
|
||||
userService := service.UserService{}
|
||||
user, err := userService.GetFirstUser()
|
||||
if err != nil {
|
||||
return common.NewError("get x-ui user failed:", err)
|
||||
}
|
||||
|
||||
inbounds := make([]*model.Inbound, 0)
|
||||
for _, v2inbound := range v2Inbounds {
|
||||
inbounds = append(inbounds, v2inbound.ToInbound(user.Id))
|
||||
}
|
||||
|
||||
inboundService := service.InboundService{}
|
||||
err = inboundService.AddInbounds(inbounds)
|
||||
if err != nil {
|
||||
return common.NewError("add x-ui inbounds failed:", err)
|
||||
}
|
||||
|
||||
fmt.Println("migrate v2-ui inbounds success:", len(inbounds))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -240,6 +240,7 @@ body {
|
||||
.ant-card-dark .ant-collapse-content,
|
||||
.ant-card-dark .ant-calendar,
|
||||
.ant-card-dark .ant-table-placeholder,
|
||||
.ant-card-dark .ant-select-selection__choice,
|
||||
.ant-card-dark .ant-input-group-addon {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #262f3d;
|
||||
|
||||
@@ -192,6 +192,7 @@ class AllSetting {
|
||||
this.subKeyFile = "";
|
||||
this.subUpdates = 0;
|
||||
this.subEncrypt = true;
|
||||
this.subShowInfo = false;
|
||||
|
||||
this.timeLocation = "Asia/Tehran";
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ const SSMethods = {
|
||||
AES_256_GCM: 'aes-256-gcm',
|
||||
AES_128_GCM: 'aes-128-gcm',
|
||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||
@@ -35,7 +37,7 @@ const TLS_VERSION_OPTION = {
|
||||
TLS11: "1.1",
|
||||
TLS12: "1.2",
|
||||
TLS13: "1.3",
|
||||
}
|
||||
};
|
||||
|
||||
const TLS_CIPHER_OPTION = {
|
||||
RSA_AES_128_CBC: "TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
@@ -71,9 +73,9 @@ const UTLS_FINGERPRINT = {
|
||||
};
|
||||
|
||||
const ALPN_OPTION = {
|
||||
HTTP1: "http/1.1",
|
||||
H2: "h2",
|
||||
H3: "h3",
|
||||
H2: "h2",
|
||||
HTTP1: "http/1.1",
|
||||
};
|
||||
|
||||
const SNIFFING_OPTION = {
|
||||
@@ -459,8 +461,8 @@ class GrpcStreamSettings extends XrayCommonClass {
|
||||
|
||||
class TlsStreamSettings extends XrayCommonClass {
|
||||
constructor(serverName='',
|
||||
minVersion = TLS_VERSION_OPTION.TLS10,
|
||||
maxVersion = TLS_VERSION_OPTION.TLS12,
|
||||
minVersion = TLS_VERSION_OPTION.TLS12,
|
||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||
cipherSuites = '',
|
||||
rejectUnknownSni = false,
|
||||
certificates=[new TlsStreamSettings.Cert()],
|
||||
@@ -522,13 +524,14 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') {
|
||||
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='', ocspStapling=3600) {
|
||||
super();
|
||||
this.useFile = useFile;
|
||||
this.certFile = certificateFile;
|
||||
this.keyFile = keyFile;
|
||||
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
|
||||
this.key = key instanceof Array ? key.join('\n') : key;
|
||||
this.ocspStapling = ocspStapling;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
@@ -536,13 +539,15 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||
return new TlsStreamSettings.Cert(
|
||||
true,
|
||||
json.certificateFile,
|
||||
json.keyFile,
|
||||
json.keyFile, '', '',
|
||||
json.ocspStapling,
|
||||
);
|
||||
} else {
|
||||
return new TlsStreamSettings.Cert(
|
||||
false, '', '',
|
||||
json.certificate.join('\n'),
|
||||
json.key.join('\n'),
|
||||
json.ocspStapling,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -552,11 +557,13 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||
return {
|
||||
certificateFile: this.certFile,
|
||||
keyFile: this.keyFile,
|
||||
ocspStapling: this.ocspStapling,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
certificate: this.cert.split('\n'),
|
||||
key: this.key.split('\n'),
|
||||
ocspStapling: this.ocspStapling,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -669,6 +676,35 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||
}
|
||||
};
|
||||
|
||||
class SockoptStreamSettings extends XrayCommonClass {
|
||||
constructor(acceptProxyProtocol = false, tcpFastOpen = false, mark = 0, tproxy="off") {
|
||||
super();
|
||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||
this.tcpFastOpen = tcpFastOpen;
|
||||
this.mark = mark;
|
||||
this.tproxy = tproxy;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
if (Object.keys(json).length === 0) return undefined;
|
||||
return new SockoptStreamSettings(
|
||||
json.acceptProxyProtocol,
|
||||
json.tcpFastOpen,
|
||||
json.mark,
|
||||
json.tproxy,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||
tcpFastOpen: this.tcpFastOpen,
|
||||
mark: this.mark,
|
||||
tproxy: this.tproxy,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class StreamSettings extends XrayCommonClass {
|
||||
constructor(network='tcp',
|
||||
security='none',
|
||||
@@ -680,6 +716,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
httpSettings=new HttpStreamSettings(),
|
||||
quicSettings=new QuicStreamSettings(),
|
||||
grpcSettings=new GrpcStreamSettings(),
|
||||
sockopt = undefined,
|
||||
) {
|
||||
super();
|
||||
this.network = network;
|
||||
@@ -692,6 +729,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
this.http = httpSettings;
|
||||
this.quic = quicSettings;
|
||||
this.grpc = grpcSettings;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
get isTls() {
|
||||
@@ -718,6 +756,14 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get sockoptSwitch() {
|
||||
return this.sockopt != undefined;
|
||||
}
|
||||
|
||||
set sockoptSwitch(value) {
|
||||
this.sockopt = value ? new SockoptStreamSettings() : undefined;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
|
||||
return new StreamSettings(
|
||||
@@ -731,6 +777,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
HttpStreamSettings.fromJson(json.httpSettings),
|
||||
QuicStreamSettings.fromJson(json.quicSettings),
|
||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||
SockoptStreamSettings.fromJson(json.sockopt),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -747,6 +794,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -904,7 +952,7 @@ class Inbound extends XrayCommonClass {
|
||||
} else if (this.isWs) {
|
||||
return this.stream.ws.path;
|
||||
} else if (this.isH2) {
|
||||
return this.stream.http.path[0];
|
||||
return this.stream.http.path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1279,8 +1327,8 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
|
||||
let password = new Array();
|
||||
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
|
||||
if (this.isSS2022) password.push(settings.password);
|
||||
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
|
||||
|
||||
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
|
||||
const url = new URL(link);
|
||||
@@ -1411,10 +1459,10 @@ class Inbound extends XrayCommonClass {
|
||||
JSON.parse(this.settings).clients.forEach((client,index) => {
|
||||
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
||||
this.stream.tls.settings.domains.forEach((domain) => {
|
||||
link += this.genLink(domain.domain, remark + '-' + client.email + '-' + domain.remark, index) + '\r\n';
|
||||
link += this.genLink(domain.domain, [remark, client.email, domain.remark].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
||||
});
|
||||
} else {
|
||||
link += this.genLink(address, remark + '-' + client.email, index) + '\r\n';
|
||||
link += this.genLink(address, [remark, client.email].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
||||
}
|
||||
});
|
||||
return link;
|
||||
@@ -1492,11 +1540,9 @@ Inbound.Settings = class extends XrayCommonClass {
|
||||
|
||||
Inbound.VmessSettings = class extends Inbound.Settings {
|
||||
constructor(protocol,
|
||||
vmesses=[new Inbound.VmessSettings.Vmess()],
|
||||
disableInsecureEncryption=false) {
|
||||
vmesses=[new Inbound.VmessSettings.Vmess()]) {
|
||||
super(protocol);
|
||||
this.vmesses = vmesses;
|
||||
this.disableInsecure = disableInsecureEncryption;
|
||||
}
|
||||
|
||||
indexOfVmessById(id) {
|
||||
@@ -1521,19 +1567,17 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
||||
return new Inbound.VmessSettings(
|
||||
Protocols.VMESS,
|
||||
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
|
||||
ObjectUtil.isEmpty(json.disableInsecureEncryption) ? false : json.disableInsecureEncryption,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
||||
disableInsecureEncryption: this.disableInsecure,
|
||||
};
|
||||
}
|
||||
};
|
||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomText(16,16)) {
|
||||
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.email = email;
|
||||
@@ -1621,7 +1665,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||
|
||||
};
|
||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomText(16,16)) {
|
||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.flow = flow;
|
||||
@@ -1742,7 +1786,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||
}
|
||||
};
|
||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
constructor(password=RandomUtil.randomSeq(10), email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomText(16,16)) {
|
||||
constructor(password=RandomUtil.randomSeq(10), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||
super();
|
||||
this.password = password;
|
||||
this.email = email;
|
||||
@@ -1878,7 +1922,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||
};
|
||||
|
||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomText(16,16)) {
|
||||
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||
super();
|
||||
this.method = method;
|
||||
this.password = password;
|
||||
|
||||
@@ -75,17 +75,7 @@ class PromiseUtil {
|
||||
}
|
||||
}
|
||||
|
||||
const seq = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
||||
'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
||||
'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z'
|
||||
];
|
||||
const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||
|
||||
class RandomUtil {
|
||||
static randomIntRange(min, max) {
|
||||
@@ -121,16 +111,6 @@ class RandomUtil {
|
||||
});
|
||||
}
|
||||
|
||||
static randomText(minLen = 6, varLen = 5) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
var len = minLen + Math.floor(Math.random() * varLen);
|
||||
for (var ii = 0; ii < len; ii++) {
|
||||
string += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
static randomShadowsocksPassword() {
|
||||
let array = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(array);
|
||||
|
||||
@@ -65,6 +65,7 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
||||
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
||||
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
|
||||
"subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() },
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
|
||||
@@ -55,6 +55,7 @@ type AllSetting struct {
|
||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||
}
|
||||
|
||||
func (s *AllSetting) CheckValid() error {
|
||||
|
||||
@@ -35,15 +35,16 @@
|
||||
this.inbound = dbInbound.toInbound();
|
||||
settings = JSON.parse(this.inbound.settings);
|
||||
this.client = settings.clients[clientIndex];
|
||||
remark = this.dbInbound.remark + ( this.client ? "-" + this.client.email : '');
|
||||
remark = [this.dbInbound.remark, ( this.client ? this.client.email : '')].filter(Boolean).join('-');
|
||||
address = this.dbInbound.address;
|
||||
this.subId = '';
|
||||
this.qrcodes = [];
|
||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
||||
this.qrcodes.push({
|
||||
remark: remark + "-" + domain.remark,
|
||||
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, clientIndex)
|
||||
remark: remarkText,
|
||||
link: this.inbound.genLink(domain.domain, remarkText, clientIndex)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -84,7 +85,7 @@
|
||||
},
|
||||
genSubLink(subID) {
|
||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||
return buildURL({ host, port, isTLS, base, path: subID });
|
||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+subID });
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
|
||||
@@ -119,15 +119,6 @@
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getNewEmail(client) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
var len = 6 + Math.floor(Math.random() * 5);
|
||||
for(var ii=0; ii<len; ii++){
|
||||
string += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
client.email = string;
|
||||
},
|
||||
resetClientTraffic(email,dbInboundId,iconElement) {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
@@ -47,7 +47,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email && app.subSettings.enable">
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
{{define "form/http"}}
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "username"}}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form-item>
|
||||
<a-row>
|
||||
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
||||
<a-input style="width: 45%" v-model.trim="account.user"
|
||||
addon-before='{{ i18n "username" }}'></a-input>
|
||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
||||
addon-before='{{ i18n "password" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -11,7 +11,7 @@
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
@@ -31,7 +31,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email && app.subSettings.enable">
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||
|
||||
@@ -10,24 +10,25 @@
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="inbound.settings.auth === 'password'">
|
||||
<tr>
|
||||
<td>{{ i18n "username" }}</td>
|
||||
<td>
|
||||
<tr v-if="inbound.settings.auth === 'password'">
|
||||
<td colspan="2">
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||
<a-row>
|
||||
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
||||
<a-input style="width: 45%" v-model.trim="account.user"
|
||||
addon-before='{{ i18n "username" }}'></a-input>
|
||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
||||
addon-before='{{ i18n "password" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.enable" }} udp</td>
|
||||
<td>
|
||||
@@ -36,10 +37,10 @@
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr v-if="inbound.settings.udp">
|
||||
<td>IP</td>
|
||||
<td>
|
||||
<a-form-item v-if="inbound.settings.udp">
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
@@ -28,7 +28,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email && app.subSettings.enable">
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "form/vless"}}
|
||||
<a-form layout="inline">
|
||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>
|
||||
@@ -10,7 +10,7 @@
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
@@ -39,7 +39,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email && app.subSettings.enable">
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "form/vmess"}}
|
||||
<a-form layout="inline">
|
||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>
|
||||
@@ -10,7 +10,7 @@
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
@@ -28,7 +28,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email && app.subSettings.enable">
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||
@@ -110,9 +110,4 @@
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
||||
<a-switch v-model="inbound.settings.disableInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{{define "form/sniffing"}}
|
||||
<a-form layout="inline">
|
||||
<a-divider dashed style="margin:0;">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
sniffing
|
||||
@@ -12,6 +13,7 @@
|
||||
</span>
|
||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||
</a-form-item>
|
||||
</a-divider>
|
||||
<a-form-item>
|
||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{{define "form/streamSettings"}}
|
||||
<!-- select stream network -->
|
||||
<a-form layout="inline">
|
||||
<a-divider dashed style="margin:0;">
|
||||
<a-form-item label="{{ i18n "transmission" }}">
|
||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
@@ -12,6 +13,7 @@
|
||||
<a-select-option value="grpc">grpc</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-divider>
|
||||
</a-form>
|
||||
|
||||
<!-- tcp -->
|
||||
@@ -43,4 +45,8 @@
|
||||
<template v-if="inbound.stream.network === 'grpc'">
|
||||
{{template "form/streamGRPC"}}
|
||||
</template>
|
||||
<!-- sockopt -->
|
||||
<template>
|
||||
{{template "form/streamSockopt"}}
|
||||
</template>
|
||||
{{end}}
|
||||
48
web/html/xui/form/stream/stream_sockopt.html
Normal file
48
web/html/xui/form/stream/stream_sockopt.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{{define "form/streamSockopt"}}
|
||||
<a-form layout="inline">
|
||||
<a-divider dashed style="margin:0;">
|
||||
<a-form-item label="Transparent Proxy">
|
||||
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||
</a-form-item>
|
||||
</a-divider>
|
||||
<table width="100%" class="ant-table-tbody" v-if="inbound.stream.sockoptSwitch">
|
||||
<tr>
|
||||
<td>Accept Proxy Protocol</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TCP FastOpen</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Route Mark</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>T-Proxy</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="off">OFF</a-select-option>
|
||||
<a-select-option value="redirect">Redirect</a-select-option>
|
||||
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,6 +1,7 @@
|
||||
{{define "form/tlsSettings"}}
|
||||
<!-- tls enable -->
|
||||
<a-form v-if="inbound.canSetTls()" layout="inline">
|
||||
<a-divider dashed style="margin:0;">
|
||||
<a-form-item label="TLS">
|
||||
<a-switch v-model="inbound.tls">
|
||||
</a-switch>
|
||||
@@ -8,6 +9,7 @@
|
||||
<a-form-item v-if="inbound.canEnableReality()" label="Reality">
|
||||
<a-switch v-model="inbound.reality"></a-switch>
|
||||
</a-form-item>
|
||||
</a-divider>
|
||||
</a-form>
|
||||
|
||||
<!-- tls settings -->
|
||||
@@ -94,12 +96,16 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Alpn</td>
|
||||
<td>ALPN</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-checkbox-group v-model="inbound.stream.tls.alpn">
|
||||
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
style="width: 250px"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="inbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -174,6 +180,14 @@
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr>
|
||||
<td>ocspStapling</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</a-form>
|
||||
|
||||
@@ -45,17 +45,28 @@
|
||||
</template>
|
||||
</table>
|
||||
</td></tr>
|
||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||
<td v-if="inbound.tls">
|
||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else-if="inbound.reality">
|
||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
||||
</td>
|
||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||
<td v-if="inbound.tls">
|
||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else-if="inbound.reality">
|
||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
||||
</td>
|
||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<td>{{ i18n "encryption" }}</td>
|
||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||
</tr><tr v-if="inbound.isSS2022">
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||
</tr><tr>
|
||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
<template v-if="infoModal.clientSettings">
|
||||
@@ -163,19 +174,7 @@
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-divider></a-divider>
|
||||
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<th>{{ i18n "encryption" }}</th>
|
||||
<th>{{ i18n "password" }}</th>
|
||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||
</tr><tr>
|
||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
<template v-if="inbound.protocol == Protocols.SHADOWSOCKS && !inbound.isSSMultiUser">
|
||||
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
|
||||
<a-divider>URL</a-divider>
|
||||
<a-row v-for="(link,index) in infoModal.links">
|
||||
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||
@@ -201,17 +200,19 @@
|
||||
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
<table v-if="inbound.protocol == Protocols.SOCKS" style="margin-bottom: 10px; width: 100%;">
|
||||
<table v-if="dbInbound.isSocks" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<th>{{ i18n "password" }} Auth</th>
|
||||
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||
<th>IP</th>
|
||||
</tr><tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||
</tr><tr v-if="inbound.settings.auth == 'password'">
|
||||
</tr>
|
||||
<template v-if="inbound.settings.auth == 'password'">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ i18n "username" }}</td>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
@@ -220,9 +221,9 @@
|
||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</table>
|
||||
<table v-if="inbound.protocol == Protocols.HTTP" style="margin-bottom: 10px; width: 100%;">
|
||||
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>{{ i18n "username" }}</th>
|
||||
@@ -233,7 +234,6 @@
|
||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
</template>
|
||||
</a-modal>
|
||||
<script>
|
||||
@@ -260,14 +260,15 @@
|
||||
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
||||
this.isExpired = this.inbound.isExpiry(index);
|
||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||
remark = this.dbInbound.remark + ( this.clientSettings ? "-" + this.clientSettings.email : '');
|
||||
remark = [this.dbInbound.remark, ( this.clientSettings ? this.clientSettings.email : '')].filter(Boolean).join('-');
|
||||
address = this.dbInbound.address;
|
||||
this.links = [];
|
||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
||||
this.links.push({
|
||||
remark: remark + "-" + domain.remark,
|
||||
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, index)
|
||||
remark: remarkText,
|
||||
link: this.inbound.genLink(domain.domain, remarkText, index)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -291,7 +292,7 @@
|
||||
},
|
||||
genSubLink(subID) {
|
||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||
return buildURL({ host, port, isTLS, base, path: subID });
|
||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+subID });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
if (this.inModal.inbound.settings.shadowsockses.length ==0){
|
||||
this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
|
||||
}
|
||||
if (["aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305"].includes(this.inModal.inbound.settings.method)) {
|
||||
if (!this.inModal.inbound.isSS2022) {
|
||||
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||
client.method = this.inModal.inbound.settings.method;
|
||||
})
|
||||
@@ -139,15 +139,6 @@
|
||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||
},
|
||||
getNewEmail(client) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
var len = 6 + Math.floor(Math.random() * 5);
|
||||
for(var ii=0; ii<len; ii++){
|
||||
string += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
client.email = string;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
.ant-col-sm-24 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
tr.hideExpandIcon .ant-table-row-expand-icon {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
@@ -121,8 +124,12 @@
|
||||
</a-radio-group>
|
||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||
:data-source="searchedInbounds"
|
||||
:loading="spinning" :scroll="{ x: 1300 }"
|
||||
:loading="spinning" :scroll="{ x: 1200 }"
|
||||
:pagination="false"
|
||||
:expand-icon-as-cell="false"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
:row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
|
||||
style="margin-top: 20px"
|
||||
@change="() => getDBInbounds()">
|
||||
<template slot="action" slot-scope="text, dbInbound">
|
||||
@@ -137,7 +144,7 @@
|
||||
<a-icon type="qrcode"></a-icon>
|
||||
{{ i18n "qrCode" }}
|
||||
</a-menu-item>
|
||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.toInbound().isSSMultiUser">
|
||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser)">
|
||||
<a-menu-item key="addClient">
|
||||
<a-icon type="user-add"></a-icon>
|
||||
{{ i18n "pages.client.add"}}
|
||||
@@ -254,6 +261,7 @@
|
||||
:columns="innerColumns"
|
||||
:data-source="getInboundClients(record)"
|
||||
:pagination="false"
|
||||
style="margin-left: 20px;"
|
||||
>
|
||||
{{template "client_table"}}
|
||||
</a-table>
|
||||
@@ -263,6 +271,7 @@
|
||||
:columns="innerTrojanColumns"
|
||||
:data-source="getInboundClients(record)"
|
||||
:pagination="false"
|
||||
style="margin-left: 20px;"
|
||||
>
|
||||
{{template "client_table"}}
|
||||
</a-table>
|
||||
@@ -278,6 +287,11 @@
|
||||
{{template "component/themeSwitcher" .}}
|
||||
<script>
|
||||
const columns = [{
|
||||
title: "ID",
|
||||
align: 'right',
|
||||
dataIndex: "id",
|
||||
width: 30,
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||
align: 'center',
|
||||
width: 30,
|
||||
@@ -287,11 +301,6 @@
|
||||
align: 'center',
|
||||
width: 30,
|
||||
scopedSlots: { customRender: 'enable' },
|
||||
}, {
|
||||
title: "ID",
|
||||
align: 'center',
|
||||
dataIndex: "id",
|
||||
width: 20,
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||
align: 'center',
|
||||
@@ -328,8 +337,8 @@
|
||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 60, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 55, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: 'UUID', width: 120, dataIndex: "id" },
|
||||
];
|
||||
|
||||
@@ -338,8 +347,8 @@
|
||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: 'Password', width: 120, dataIndex: "password" },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 55, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: '{{ i18n "password" }}', width: 165, dataIndex: "password" },
|
||||
];
|
||||
|
||||
const app = new Vue({
|
||||
@@ -717,8 +726,7 @@
|
||||
});
|
||||
},
|
||||
findIndexOfClient(clients, client) {
|
||||
firstKey = Object.keys(client)[0];
|
||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||
return clients.findIndex(item => JSON.stringify(item) === JSON.stringify(client));
|
||||
},
|
||||
async addClient(clients, dbInboundId) {
|
||||
const data = {
|
||||
@@ -824,6 +832,7 @@
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||
client.enable = !client.enable; // For finding correct index in findIndexOfClient() function
|
||||
index = this.findIndexOfClient(clients, client);
|
||||
clients[index].enable = !clients[index].enable;
|
||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
:stroke-color="status.cpu.color"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:percent="status.cpu.percent"></a-progress>
|
||||
<div>CPU</div>
|
||||
<div>CPU ([[ status.cpuCount ]]core)</div>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
@@ -92,13 +92,16 @@
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "pages.index.operationHours" }}:
|
||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||
xray
|
||||
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||
os
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.operationHoursDesc" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
@@ -131,7 +134,36 @@
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
tcp / udp {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
|
||||
{{ i18n "usage"}}:
|
||||
Memory [[ sizeFormat(status.appStats.mem) ]] -
|
||||
Threads [[ status.appStats.threads ]]
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
Host: [[ status.hostInfo.hostname ]] -
|
||||
<template v-if="status.hostInfo.ipv4">IPv4:
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
[[ status.hostInfo.ipv4 ]]
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="status.hostInfo.ipv6">IPv6:
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
[[ status.hostInfo.ipv6 ]]
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "pages.index.connectionCount" }}: tcp: [[ status.tcpCount ]] udp: [[ status.udpCount ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.connectionCountDesc" }}
|
||||
@@ -263,7 +295,7 @@
|
||||
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
||||
[[ backupModal.description ]]
|
||||
</p>
|
||||
<a-space direction="horizontal" align="center" style="margin-bottom: 10px;">
|
||||
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
||||
<a-button type="primary" @click="exportDatabase()">
|
||||
[[ backupModal.exportText ]]
|
||||
</a-button>
|
||||
@@ -315,6 +347,7 @@
|
||||
class Status {
|
||||
constructor(data) {
|
||||
this.cpu = new CurTotal(0, 0);
|
||||
this.cpuCount = 0;
|
||||
this.disk = new CurTotal(0, 0);
|
||||
this.loads = [0, 0, 0];
|
||||
this.mem = new CurTotal(0, 0);
|
||||
@@ -324,12 +357,16 @@
|
||||
this.tcpCount = 0;
|
||||
this.udpCount = 0;
|
||||
this.uptime = 0;
|
||||
this.appUptime = 0;
|
||||
this.appStats = {threads: 0, mem: 0, uptime: 0};
|
||||
this.hostInfo = {hostname:"", ipv4: "", ipv6: ""};
|
||||
this.xray = {state: State.Stop, errorMsg: "", version: "", color: ""};
|
||||
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
this.cpu = new CurTotal(data.cpu, 100);
|
||||
this.cpuCount = data.cpuCount;
|
||||
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
||||
this.loads = data.loads.map(load => toFixed(load, 2));
|
||||
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
||||
@@ -339,6 +376,9 @@
|
||||
this.tcpCount = data.tcpCount;
|
||||
this.udpCount = data.udpCount;
|
||||
this.uptime = data.uptime;
|
||||
this.appUptime = data.appUptime;
|
||||
this.appStats = data.appStats;
|
||||
this.hostInfo = data.hostInfo;
|
||||
this.xray = data.xray;
|
||||
switch (this.xray.state) {
|
||||
case State.Running:
|
||||
|
||||
@@ -338,6 +338,7 @@
|
||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort"></setting-list-item>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
)
|
||||
|
||||
type CheckInboundJob struct {
|
||||
xrayService service.XrayService
|
||||
inboundService service.InboundService
|
||||
}
|
||||
|
||||
func NewCheckInboundJob() *CheckInboundJob {
|
||||
return new(CheckInboundJob)
|
||||
}
|
||||
|
||||
func (j *CheckInboundJob) Run() {
|
||||
needRestart, count, err := j.inboundService.DisableInvalidClients()
|
||||
if err != nil {
|
||||
logger.Warning("Error in disabling invalid clients:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("%v clients disabled", count)
|
||||
if needRestart {
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
needRestart, count, err = j.inboundService.DisableInvalidInbounds()
|
||||
if err != nil {
|
||||
logger.Warning("Error in disabling invalid inbounds:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("%v inbounds disabled", count)
|
||||
if needRestart {
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,12 @@ func (j *XrayTrafficJob) Run() {
|
||||
logger.Warning("get xray traffic failed:", err)
|
||||
return
|
||||
}
|
||||
err = j.inboundService.AddTraffic(traffics)
|
||||
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||
if err != nil {
|
||||
logger.Warning("add traffic failed:", err)
|
||||
}
|
||||
|
||||
err = j.inboundService.AddClientTraffic(clientTraffics)
|
||||
if err != nil {
|
||||
logger.Warning("add client traffic failed:", err)
|
||||
if needRestart {
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -194,38 +194,6 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||
return inbound, needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) AddInbounds(inbounds []*model.Inbound) error {
|
||||
for _, inbound := range inbounds {
|
||||
exist, err := s.checkPortExist(inbound.Port, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
return common.NewError("Port already exists:", inbound.Port)
|
||||
}
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
var err error
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, inbound := range inbounds {
|
||||
err = tx.Save(inbound).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) DelInbound(id int) (bool, error) {
|
||||
db := database.GetDB()
|
||||
|
||||
@@ -659,35 +627,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
return needRestart, tx.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
|
||||
if len(traffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Update traffics in a single transaction
|
||||
err := database.GetDB().Transaction(func(tx *gorm.DB) error {
|
||||
for _, traffic := range traffics {
|
||||
if traffic.IsInbound {
|
||||
update := tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
||||
Updates(map[string]interface{}{
|
||||
"up": gorm.Expr("up + ?", traffic.Up),
|
||||
"down": gorm.Expr("down + ?", traffic.Down),
|
||||
})
|
||||
if update.Error != nil {
|
||||
return update.Error
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
|
||||
if len(traffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||
var err error
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
|
||||
@@ -698,13 +639,64 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
||||
tx.Commit()
|
||||
}
|
||||
}()
|
||||
err = s.addInboundTraffic(tx, inboundTraffics)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
err = s.addClientTraffic(tx, clientTraffics)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
needRestart1, count, err := s.disableInvalidClients(tx)
|
||||
if err != nil {
|
||||
logger.Warning("Error in disabling invalid clients:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("%v clients disabled", count)
|
||||
}
|
||||
|
||||
needRestart2, count, err := s.disableInvalidInbounds(tx)
|
||||
if err != nil {
|
||||
logger.Warning("Error in disabling invalid inbounds:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("%v inbounds disabled", count)
|
||||
}
|
||||
return nil, (needRestart1 || needRestart2)
|
||||
}
|
||||
|
||||
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
||||
if len(traffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
for _, traffic := range traffics {
|
||||
if traffic.IsInbound {
|
||||
err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
||||
Updates(map[string]interface{}{
|
||||
"up": gorm.Expr("up + ?", traffic.Up),
|
||||
"down": gorm.Expr("down + ?", traffic.Down),
|
||||
}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
||||
if len(traffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
emails := make([]string, 0, len(traffics))
|
||||
for _, traffic := range traffics {
|
||||
emails = append(emails, traffic.Email)
|
||||
}
|
||||
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
|
||||
err = db.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
|
||||
err = tx.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -789,14 +781,13 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
||||
return dbClientTraffics, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
||||
db := database.GetDB()
|
||||
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
||||
now := time.Now().Unix() * 1000
|
||||
needRestart := false
|
||||
|
||||
if p != nil {
|
||||
var tags []string
|
||||
err := db.Table("inbounds").
|
||||
err := tx.Table("inbounds").
|
||||
Select("inbounds.tag").
|
||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||
Scan(&tags).Error
|
||||
@@ -816,7 +807,7 @@ func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
|
||||
result := db.Model(model.Inbound{}).
|
||||
result := tx.Model(model.Inbound{}).
|
||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||
Update("enable", false)
|
||||
err := result.Error
|
||||
@@ -824,8 +815,7 @@ func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
||||
return needRestart, count, err
|
||||
}
|
||||
|
||||
func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
||||
db := database.GetDB()
|
||||
func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) {
|
||||
now := time.Now().Unix() * 1000
|
||||
needRestart := false
|
||||
|
||||
@@ -835,7 +825,7 @@ func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
||||
Email string
|
||||
}
|
||||
|
||||
err := db.Table("inbounds").
|
||||
err := tx.Table("inbounds").
|
||||
Select("inbounds.tag, client_traffics.email").
|
||||
Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id").
|
||||
Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true).
|
||||
@@ -855,7 +845,7 @@ func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
result := db.Model(xray.ClientTraffic{}).
|
||||
result := tx.Model(xray.ClientTraffic{}).
|
||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||
Update("enable", false)
|
||||
err := result.Error
|
||||
|
||||
@@ -39,9 +39,10 @@ const (
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
T time.Time `json:"-"`
|
||||
Cpu float64 `json:"cpu"`
|
||||
Mem struct {
|
||||
T time.Time `json:"-"`
|
||||
Cpu float64 `json:"cpu"`
|
||||
CpuCount int `json:"cpuCount"`
|
||||
Mem struct {
|
||||
Current uint64 `json:"current"`
|
||||
Total uint64 `json:"total"`
|
||||
} `json:"mem"`
|
||||
@@ -70,6 +71,16 @@ type Status struct {
|
||||
Sent uint64 `json:"sent"`
|
||||
Recv uint64 `json:"recv"`
|
||||
} `json:"netTraffic"`
|
||||
AppStats struct {
|
||||
Threads uint32 `json:"threads"`
|
||||
Mem uint64 `json:"mem"`
|
||||
Uptime uint64 `json:"uptime"`
|
||||
} `json:"appStats"`
|
||||
HostInfo struct {
|
||||
HostName string `json:"hostname"`
|
||||
Ipv4 string `json:"ipv4"`
|
||||
Ipv6 string `json:"ipv6"`
|
||||
} `json:"hostInfo"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
@@ -176,6 +187,36 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
}
|
||||
status.Xray.Version = s.xrayService.GetXrayVersion()
|
||||
|
||||
var rtm runtime.MemStats
|
||||
runtime.ReadMemStats(&rtm)
|
||||
|
||||
status.AppStats.Mem = rtm.Sys
|
||||
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
||||
status.CpuCount = runtime.NumCPU()
|
||||
if p.IsRunning() {
|
||||
status.AppStats.Uptime = p.GetUptime()
|
||||
} else {
|
||||
status.AppStats.Uptime = 0
|
||||
}
|
||||
|
||||
status.HostInfo.HostName, _ = os.Hostname()
|
||||
|
||||
// get ip address
|
||||
netInterfaces, _ := net.Interfaces()
|
||||
for i := 0; i < len(netInterfaces); i++ {
|
||||
if len(netInterfaces[i].Flags) > 2 && netInterfaces[i].Flags[0] == "up" && netInterfaces[i].Flags[1] != "loopback" {
|
||||
addrs := netInterfaces[i].Addrs
|
||||
|
||||
for _, address := range addrs {
|
||||
if strings.Contains(address.Addr, ".") {
|
||||
status.HostInfo.Ipv4 += address.Addr + " "
|
||||
} else if address.Addr[0:6] != "fe80::" {
|
||||
status.HostInfo.Ipv6 += address.Addr + " "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ var defaultValueMap = map[string]string{
|
||||
"subKeyFile": "",
|
||||
"subUpdates": "12",
|
||||
"subEncrypt": "true",
|
||||
"subShowInfo": "false",
|
||||
}
|
||||
|
||||
type SettingService struct {
|
||||
@@ -377,6 +378,10 @@ func (s *SettingService) GetSubEncrypt() (bool, error) {
|
||||
return s.getBool("subEncrypt")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubShowInfo() (bool, error) {
|
||||
return s.getBool("subShowInfo")
|
||||
}
|
||||
|
||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||
if err := allSetting.CheckValid(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -69,7 +69,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.inboundService.DisableInvalidClients()
|
||||
s.inboundService.AddTraffic(nil, nil)
|
||||
|
||||
inbounds, err := s.inboundService.GetAllInbounds()
|
||||
if err != nil {
|
||||
|
||||
@@ -127,7 +127,6 @@
|
||||
"network" = "Network"
|
||||
"destinationPort" = "Destination Port"
|
||||
"targetAddress" = "Target Address"
|
||||
"disableInsecureEncryption" = "Disable Insecure Encryption"
|
||||
"monitorDesc" = "Leave blank by default"
|
||||
"meansNoLimit" = "Means No Limit"
|
||||
"totalFlow" = "Total Flow"
|
||||
@@ -268,7 +267,8 @@
|
||||
"subUpdatesDesc" = "Interval hours between updates in client application"
|
||||
"subEncrypt" = "Encrypt configs"
|
||||
"subEncryptDesc" = "Encrypt the returned configs in subscription"
|
||||
|
||||
"subShowInfo" = "Show usage info"
|
||||
"subShowInfoDesc" = "Show remianed traffic and date after config name"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "Templates"
|
||||
|
||||
@@ -127,7 +127,6 @@
|
||||
"network" = "شبکه"
|
||||
"destinationPort" = "پورت مقصد"
|
||||
"targetAddress" = "آدرس مقصد"
|
||||
"disableInsecureEncryption" = "غیرفعال سازی رمزگذاری ناامن"
|
||||
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
||||
"meansNoLimit" = "یعنی بدون محدودیت"
|
||||
"totalFlow" = "کل ترافیک"
|
||||
@@ -267,6 +266,8 @@
|
||||
"subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر"
|
||||
"subEncrypt" = "رمزگذاری کانفیگ ها"
|
||||
"subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن"
|
||||
"subShowInfo" = "نمایش اطلاعات مصرف"
|
||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "الگوها"
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
"username" = "имя пользователя"
|
||||
"password" = "пароль"
|
||||
"login" = "логин"
|
||||
"confirm" = "подтвердить"
|
||||
"cancel" = "отмена"
|
||||
"close" = "закрыть"
|
||||
"copy" = "копировать"
|
||||
"copied" = "скопировано"
|
||||
"download" = "скачать"
|
||||
"remark" = "примечание"
|
||||
"enable" = "включить"
|
||||
"protocol" = "протокол"
|
||||
"search" = "поиск"
|
||||
"username" = "Имя пользователя"
|
||||
"password" = "Пароль"
|
||||
"login" = "Войти"
|
||||
"confirm" = "Подтвердить"
|
||||
"cancel" = "Отмена"
|
||||
"close" = "Закрыть"
|
||||
"copy" = "Копировать"
|
||||
"copied" = "Скопировано"
|
||||
"download" = "Скачать"
|
||||
"remark" = "Примечание"
|
||||
"enable" = "Включить"
|
||||
"protocol" = "Протокол"
|
||||
"search" = "Поиск"
|
||||
"filter" = "Фильтр"
|
||||
"loading" = "загрузка"
|
||||
"second" = "секунда"
|
||||
"minute" = "минута"
|
||||
"hour" = "час"
|
||||
"day" = "день"
|
||||
"loading" = "Загрузка"
|
||||
"second" = "Секунда"
|
||||
"minute" = "Минута"
|
||||
"hour" = "Час"
|
||||
"day" = "День"
|
||||
"check" = "просмотр"
|
||||
"indefinite" = "бессрочно"
|
||||
"unlimited" = "безлимитно"
|
||||
"none" = "пусто"
|
||||
"indefinite" = "Бессрочно"
|
||||
"unlimited" = "Безлимитно"
|
||||
"none" = "Пусто"
|
||||
"qrCode" = "QR-код"
|
||||
"info" = "больше информации"
|
||||
"edit" = "изменить"
|
||||
"delete" = "удалить"
|
||||
"reset" = "обнулить"
|
||||
"copySuccess" = "скопировано"
|
||||
"sure" = "да"
|
||||
"info" = "Больше информации"
|
||||
"edit" = "Изменить"
|
||||
"delete" = "Удалить"
|
||||
"reset" = "Обнулить"
|
||||
"copySuccess" = "Успешно скопировано"
|
||||
"sure" = "Да"
|
||||
"encryption" = "Шифрование"
|
||||
"transmission" = "протокол передачи"
|
||||
"host" = "хост"
|
||||
"path" = "путь"
|
||||
"camouflage" = "маскировка"
|
||||
"status" = "статус"
|
||||
"enabled" = "включено"
|
||||
"disabled" = "отключено"
|
||||
"depleted" = "исчерпано"
|
||||
"depletingSoon" = "почти исчерпано"
|
||||
"domainName" = "домен"
|
||||
"monitor" = "порт IP"
|
||||
"certificate" = "сертификат"
|
||||
"fail" = "неудача"
|
||||
"success" = "успешно"
|
||||
"getVersion" = "узнать версию"
|
||||
"transmission" = "Протокол передачи"
|
||||
"host" = "Хост"
|
||||
"path" = "Путь"
|
||||
"camouflage" = "Маскировка"
|
||||
"status" = "Статус"
|
||||
"enabled" = "Включено"
|
||||
"disabled" = "Отключено"
|
||||
"depleted" = "Отключены"
|
||||
"depletingSoon" = "Почти отключены"
|
||||
"domainName" = "Домен"
|
||||
"monitor" = "Прослушиваемый IP"
|
||||
"certificate" = "Сертификат"
|
||||
"fail" = "Неудачно"
|
||||
"success" = "Успешно"
|
||||
"getVersion" = "Узнать версию"
|
||||
"install" = "установка"
|
||||
"clients" = "клиенты"
|
||||
"usage" = "использование"
|
||||
"remained" = "остались"
|
||||
"clients" = "Клиенты"
|
||||
"usage" = "Использовано"
|
||||
"remained" = "Осталось"
|
||||
"secAlertTitle" = "Предупреждение системы безопасности"
|
||||
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации, пока TLS не будет активирован для защиты данных"
|
||||
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "статус системы"
|
||||
"inbounds" = "пользователи"
|
||||
"settings" = "настройки"
|
||||
"logout" = "выход"
|
||||
"link" = "другое"
|
||||
"dashboard" = "Статус системы"
|
||||
"inbounds" = "Подключения"
|
||||
"settings" = "Настройки"
|
||||
"logout" = "Выйти"
|
||||
"link" = "Другое"
|
||||
|
||||
[pages.login]
|
||||
"title" = "логин"
|
||||
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
|
||||
"title" = "Войти"
|
||||
"loginAgain" = "Время сессии истекло. Пожалуйста, войдите в систему снова"
|
||||
|
||||
[pages.login.toasts]
|
||||
"invalidFormData" = "Недопустимый формат данных"
|
||||
"emptyUsername" = "Введите имя пользователя"
|
||||
"emptyPassword" = "Введите пароль"
|
||||
"wrongUsernameOrPassword" = "Неверное имя пользователя или пароль"
|
||||
"successLogin" = "успешный вход"
|
||||
"successLogin" = "Успешный вход"
|
||||
|
||||
[pages.index]
|
||||
"title" = "статус системы"
|
||||
"memory" = "память"
|
||||
"hard" = "жесткий диск"
|
||||
"xrayStatus" = "статус Xray"
|
||||
"stopXray" = "стоп"
|
||||
"restartXray" = "рестарт Xray"
|
||||
"xraySwitch" = "переключить версию"
|
||||
"title" = "Статус системы"
|
||||
"memory" = "ОЗУ"
|
||||
"hard" = "Место на диске"
|
||||
"xrayStatus" = "Статус Xray"
|
||||
"stopXray" = "Остановка"
|
||||
"restartXray" = "Перезапуск Xray"
|
||||
"xraySwitch" = "Сменить версию"
|
||||
"xraySwitchClick" = "Выберите желаемую версию"
|
||||
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями"
|
||||
"operationHours" = "Часы работы"
|
||||
"operationHoursDesc" = "Аптайм системы: время системы в сети"
|
||||
"operationHours" = "Время работы"
|
||||
"operationHoursDesc" = "Время работы системы: время с момента запуска."
|
||||
"systemLoad" = "Системная нагрузка"
|
||||
"connectionCount" = "количество соединений"
|
||||
"connectionCount" = "Количество соединений"
|
||||
"connectionCountDesc" = "Всего подключений по всем сетям»"
|
||||
"upSpeed" = "Общая скорость upload"
|
||||
"downSpeed" = "Общая скорость download"
|
||||
"upSpeed" = "Общая скорость отдачи"
|
||||
"downSpeed" = "Общая скорость получения"
|
||||
"totalSent" = "Общий объем загруженных данных с момента запуска системы"
|
||||
"totalReceive" = "Общий объем полученных данных с момента запуска системы"
|
||||
"xraySwitchVersionDialog" = "переключить версию Xray"
|
||||
"xraySwitchVersionDialog" = "Переключить версию Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
|
||||
"dontRefresh" = "Установка. Не обновляйте эту страницу"
|
||||
"logs" = "Логи"
|
||||
@@ -100,39 +100,38 @@
|
||||
"importDatabase" = "Импорт базы данных"
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "пользователи"
|
||||
"totalDownUp" = "Всего входящих/исходящих"
|
||||
"title" = "Подключения"
|
||||
"totalDownUp" = "Всего получено/отправлено"
|
||||
"totalUsage" = "Всего использовано"
|
||||
"inboundCount" = "Количество пользователей"
|
||||
"inboundCount" = "Количество подключений"
|
||||
"operate" = "Меню"
|
||||
"enable" = "Включить"
|
||||
"remark" = "Примечание"
|
||||
"protocol" = "Протокол"
|
||||
"port" = "Порт"
|
||||
"traffic" = "Траффик"
|
||||
"traffic" = "Трафик"
|
||||
"details" = "Подробнее"
|
||||
"transportConfig" = "Перенести"
|
||||
"expireDate" = "Дата окончания"
|
||||
"resetTraffic" = "Обнулить траффик"
|
||||
"addInbound" = "Добавить пользователя"
|
||||
"resetTraffic" = "Обнулить трафик"
|
||||
"addInbound" = "Добавить подключение"
|
||||
"generalActions" = "Общие действия"
|
||||
"create" = "Создать"
|
||||
"update" = "Обновить"
|
||||
"modifyInbound" = "Изменить данные"
|
||||
"deleteInbound" = "Удалить пользователя"
|
||||
"deleteInboundContent" = "Подтвердите удаление пользователя?"
|
||||
"resetTrafficContent" = "Подтвердите обнуление траффика?"
|
||||
"deleteInbound" = "Удалить подключение"
|
||||
"deleteInboundContent" = "Вы уверены, что хотите удалить подключение?"
|
||||
"resetTrafficContent" = "Подтвердите обнуление трафика?"
|
||||
"copyLink" = "Копировать ключ"
|
||||
"address" = "Адрес"
|
||||
"network" = "Сеть"
|
||||
"destinationPort" = "Порт назначения"
|
||||
"targetAddress" = "Целевой адрес"
|
||||
"disableInsecureEncryption" = "Отключить небезопасное шифрование"
|
||||
"monitorDesc" = "Оставьте пустым по умолчанию"
|
||||
"meansNoLimit" = "Значит без ограничений"
|
||||
"totalFlow" = "Общий расход"
|
||||
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы никогда не истекать"
|
||||
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
||||
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы сделать бессрочно"
|
||||
"noRecommendKeepDefault" = "Нет особых требований для сохранения настроек по умолчанию"
|
||||
"certificatePath" = "Путь файла сертификата"
|
||||
"certificateContent" = "Содержимое файла сертификата"
|
||||
"publicKeyPath" = "Путь к публичному ключу"
|
||||
@@ -143,21 +142,21 @@
|
||||
"client" = "Клиент"
|
||||
"export" = "Поделиться ключом"
|
||||
"clone" = "Клонировать"
|
||||
"cloneInbound" = "Клонировать пользователя"
|
||||
"cloneInboundContent" = "Все настройки этого пользователя, кроме порта, порт прослушки и клиентов, будут клонированы"
|
||||
"cloneInbound" = "Клонировать подключение"
|
||||
"cloneInboundContent" = "Все настройки этого подключения, кроме порта, порт прослушки и клиентов, будут клонированы"
|
||||
"cloneInboundOk" = "Клонировать"
|
||||
"resetAllTraffic" = "Обнулить весь траффик"
|
||||
"resetAllTrafficTitle" = "Обнуление всего траффика"
|
||||
"resetAllTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
|
||||
"resetInboundClientTraffics" = "Обнулить траффик пользователей"
|
||||
"resetInboundClientTrafficTitle" = "Обнуление траффика пользователей"
|
||||
"resetInboundClientTrafficContent" = "Вы уверены, что хотите обнулить весь трафик для этих пользователей?"
|
||||
"resetAllClientTraffics" = "Обнулить весь траффик пользователей"
|
||||
"resetAllClientTrafficTitle" = "Обнуление всего траффика пользователей"
|
||||
"resetAllClientTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
|
||||
"delDepletedClients" = "Удалить отключенных пользователей"
|
||||
"delDepletedClientsTitle" = "Удаление отключенных пользователей"
|
||||
"delDepletedClientsContent" = "Подтверждаете удаление отключенных пользователей?"
|
||||
"resetAllTraffic" = "Обнулить весь трафик"
|
||||
"resetAllTrafficTitle" = "Обнуление всего трафика"
|
||||
"resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех подключений?"
|
||||
"resetInboundClientTraffics" = "Обнулить трафик клиентов"
|
||||
"resetInboundClientTrafficTitle" = "Обнуление трафика клиентов"
|
||||
"resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить весь трафик для клиентов этого подключения?"
|
||||
"resetAllClientTraffics" = "Обнулить весь трафик клиентов"
|
||||
"resetAllClientTrafficTitle" = "Обнуление всего трафика клиентов"
|
||||
"resetAllClientTrafficContent" = "Вы уверены, что хотите сбросить трафик для всех клиентов?"
|
||||
"delDepletedClients" = "Удалить отключенных клиентов"
|
||||
"delDepletedClientsTitle" = "Удаление отключенных клиентов"
|
||||
"delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных клиентов?"
|
||||
"email" = "Email"
|
||||
"emailDesc" = "Пожалуйста, укажите уникальный Email"
|
||||
"setDefaultCert" = "Установить сертификат с панели"
|
||||
@@ -165,12 +164,12 @@
|
||||
"subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигов"
|
||||
|
||||
[pages.client]
|
||||
"add" = "Добавить пользователя"
|
||||
"edit" = "Редактировать пользователя"
|
||||
"submitAdd" = "Добавить пользователя"
|
||||
"add" = "Добавить клиента"
|
||||
"edit" = "Редактировать клиента"
|
||||
"submitAdd" = "Добавить клиента"
|
||||
"submitEdit" = "Сохранить изменения"
|
||||
"clientCount" = "Количество пользователей"
|
||||
"bulk" = "Добавить несколько"
|
||||
"clientCount" = "Количество клиентов"
|
||||
"bulk" = "Добавить несколько клиентов"
|
||||
"method" = "Метод"
|
||||
"first" = "Первый"
|
||||
"last" = "Последний"
|
||||
@@ -184,18 +183,18 @@
|
||||
"obtain" = "Получить"
|
||||
|
||||
[pages.inbounds.stream.general]
|
||||
"requestHeader" = "Требуется заголовок"
|
||||
"requestHeader" = "Заголовок запроса"
|
||||
"name" = "Имя"
|
||||
"value" = "Значение"
|
||||
|
||||
[pages.inbounds.stream.tcp]
|
||||
"requestVersion" = "Требуется версия"
|
||||
"requestMethod" = "Требуется метод"
|
||||
"requestPath" = "Требуется путь"
|
||||
"responseVersion" = "Указать версию"
|
||||
"responseStatus" = "Указать статус"
|
||||
"responseStatusDescription" = "Указать примечание статуса"
|
||||
"responseHeader" = "Указать заголовок"
|
||||
"requestVersion" = "Версия запроса"
|
||||
"requestMethod" = "Метод запроса"
|
||||
"requestPath" = "Петь запроса"
|
||||
"responseVersion" = "Версия ответа"
|
||||
"responseStatus" = "Статус ответа"
|
||||
"responseStatusDescription" = "Описание статуса ответа"
|
||||
"responseHeader" = "Заголовок ответа"
|
||||
|
||||
[pages.inbounds.stream.quic]
|
||||
"encryption" = "Шифрование"
|
||||
@@ -203,113 +202,115 @@
|
||||
[pages.settings]
|
||||
"title" = "Настройки"
|
||||
"save" = "Сохранить"
|
||||
"infoDesc" = "Каждое изменение здесь необходимо сохранить и перезапустить панель, чтобы оно вступило в силу"
|
||||
"restartPanel" = "Рестарт панели"
|
||||
"restartPanelDesc" = "Подтвердите рестарт панели? ОК для рестарта панели через 3 сек. Если вы не можете пользоваться панелью после рестарта, пожалуйста, посмотрите лог панели на сервере"
|
||||
"infoDesc" = "Все внесенные здесь изменения должны быть сохранены. Чтобы изменения вступили в силу, перезапустите панель."
|
||||
"restartPanel" = "Перезапуск панели"
|
||||
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Нажмите OK для перезапуска через 3 секунды. Если после перезапуска не удается получить доступ к панели, просмотрите информацию журнала панели на сервере."
|
||||
"resetDefaultConfig" = "Сбросить всё по-умолчанию"
|
||||
"panelConfig" = "Настройки панели"
|
||||
"userSettings" = "Настройки безопасности"
|
||||
"xrayConfiguration" = "Конфигурация Xray"
|
||||
"TGBotSettings" = "Настройки Телеграм-бота"
|
||||
"panelListeningIP" = "IP-порт панели"
|
||||
"panelListeningIPDesc" = "Оставьте пустым для работы с любого IP. Перезагрузите панель для применения настроек"
|
||||
"panelListeningIP" = "IP-адрес прослушивания панели"
|
||||
"panelListeningIPDesc" = "Оставьте пустым, чтобы прослушивать все IP-адреса."
|
||||
"panelListeningDomain" = "Домен прослушивания панели"
|
||||
"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса"
|
||||
"panelListeningDomainDesc" = "Оставьте пустым, чтобы прослушивать все домены и IP-адреса"
|
||||
"panelPort" = "Порт панели"
|
||||
"panelPortDesc" = "Перезагрузите панель для применения настроек"
|
||||
"panelPortDesc" = "Номер порта для доступа к панели"
|
||||
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
|
||||
"publicKeyPathDesc" = "Введите полный путь, начинающийся с «/». Перезагрузите панель для применения настроек"
|
||||
"publicKeyPathDesc" = "Введите полный путь, начинающийся с «/»."
|
||||
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
|
||||
"privateKeyPathDesc" = "Введите полный путь, начинающийся с «/». Перезагрузите панель для применения настроек"
|
||||
"privateKeyPathDesc" = "Введите полный путь, начинающийся с «/»."
|
||||
"panelUrlPath" = "Корневой путь URL-адреса панели"
|
||||
"panelUrlPathDesc" = "Должен начинаться с «/» и заканчиваться на «/». Перезагрузите панель для применения настроек"
|
||||
"oldUsername" = "Имя пользователя сейчас"
|
||||
"currentPassword" = "Пароль сейчас"
|
||||
"panelUrlPathDesc" = "Должен начинаться с «/» и заканчиваться на «/»."
|
||||
"oldUsername" = "Текущее имя пользователя"
|
||||
"currentPassword" = "Текущий пароль"
|
||||
"newUsername" = "Новое имя пользователя"
|
||||
"newPassword" = "Новый пароль"
|
||||
"telegramBotEnable" = "Включить Телеграм-бота"
|
||||
"telegramBotEnableDesc" = "Перезагрузите панель для применения настроек"
|
||||
"telegramBotEnableDesc" = "Ваш telegram-бот будет взаимодействовать с панелью"
|
||||
"telegramToken" = "Токен Телеграм-бота"
|
||||
"telegramTokenDesc" = "Перезагрузите панель для применения настроек"
|
||||
"telegramTokenDesc" = "Токен, который вы получили от @BotFather"
|
||||
"telegramChatId" = "Телеграм-ID админа бота"
|
||||
"telegramChatIdDesc" = "Множественные идентификаторы чата, разделенные запятыми. Чтобы получить свои идентификаторы чатов, используйте @userinfobot или команду '/id' в боте."
|
||||
"telegramChatIdDesc" = "Несколько идентификаторов чата, разделенных запятой. Используйте @userinfobot или команду '/id' в боте для получения идентификаторов чата."
|
||||
"telegramNotifyTime" = "Частота уведомлений телеграм-бота"
|
||||
"telegramNotifyTimeDesc" = "Используйте формат Crontab. Перезагрузите панель для применения настроек"
|
||||
"telegramNotifyTimeDesc" = "Используйте временной формат Crontab."
|
||||
"tgNotifyBackup" = "Резервное копирование базы данных"
|
||||
"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете. Перезагрузите панель для применения настроек"
|
||||
"tgNotifyBackupDesc" = "Включение отправки файла резервной копии базы данных с уведомлением об отчете"
|
||||
"tgNotifyLogin" = "Уведомление о входе"
|
||||
"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель."
|
||||
"sessionMaxAge" = "Продолжительность сессии"
|
||||
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)"
|
||||
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (единица измерения: минута)"
|
||||
"expireTimeDiff" = "Порог истечения срока сессии для уведомления"
|
||||
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)"
|
||||
"trafficDiff" = "Порог траффика для уведомления"
|
||||
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)"
|
||||
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (единица измерения: день)"
|
||||
"trafficDiff" = "Порог трафика для уведомления"
|
||||
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (единица измерения: ГБ)"
|
||||
"tgNotifyCpu" = "Порог нагрузки на ЦП для уведомления"
|
||||
"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (значение:%)"
|
||||
"timeZone" = "Временная зона"
|
||||
"timeZoneDesc" = "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе. Перезагрузите панель для применения настроек"
|
||||
"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (единица измерения:%)"
|
||||
"timeZone" = "Часовой пояс"
|
||||
"timeZoneDesc" = "Запланированные задания выполняются в соответствии со временем в данном часовом поясе."
|
||||
"subSettings" = "Подписка"
|
||||
"subEnable" = "Включить службу"
|
||||
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
||||
"subListen" = "Прослушивание IP"
|
||||
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
|
||||
"subListen" = "Прослушиваемый IP"
|
||||
"subListenDesc" = "Оставьте пустым, чтобы прослушивать все IP-адреса"
|
||||
"subPort" = "Порт подписки"
|
||||
"subPortDesc" = "Номер порта для обслуживания службы подписки не должен использоваться на сервере"
|
||||
"subPortDesc" = "Номер порта для прослушивания службы подписки не должен использоваться на сервере"
|
||||
"subCertPath" = "Путь к файлу открытого ключа сертификата подписки"
|
||||
"subCertPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
|
||||
"subKeyPath" = "Путь к файлу закрытого ключа сертификата подписки"
|
||||
"subKeyPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
|
||||
"subPath" = "Корневой путь URL-адреса подписки"
|
||||
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
||||
"subDomain" = "Домен прослушивания"
|
||||
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все домены и IP-адреса"
|
||||
"subDomain" = "Домен для прослушивания"
|
||||
"subDomainDesc" = "Оставьте пустым, чтобы прослушивать все домены и IP-адреса"
|
||||
"subUpdates" = "Интервалы обновления подписки"
|
||||
"subUpdatesDesc" = "Часовой интервал между обновлениями в клиентском приложении"
|
||||
"subEncrypt" = "Шифровать конфиги"
|
||||
"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке"
|
||||
"subEncrypt" = "Шифрование конфигураций"
|
||||
"subEncryptDesc" = "Шифрование возвращаемых конфигураций в подписке"
|
||||
"subShowInfo" = "Показать информацию об использовании"
|
||||
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "Шаблоны"
|
||||
"basicTemplate" = "Базовые шаблоны"
|
||||
"advancedTemplate" = "Расширенные шаблоны"
|
||||
"completeTemplate" = "Конфигурация шаблона"
|
||||
"completeTemplate" = "Итоговый шаблон"
|
||||
"generalConfigs" = "Основные настройки"
|
||||
"generalConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
|
||||
"blockConfigs" = "Блокировка конфигураций"
|
||||
"generalConfigsDesc" = "Общие настройки"
|
||||
"blockConfigs" = "Блокирующие конфигурации"
|
||||
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам."
|
||||
"blockCountryConfigs" = "Заблокировать конфигурации страны"
|
||||
"blockCountryConfigs" = "Конфигурация блокировки стран"
|
||||
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны."
|
||||
"directCountryConfigs" = "Прямые настройки страны"
|
||||
"directCountryConfigs" = "Прямые настройки стран"
|
||||
"directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны."
|
||||
"ipv4Configs" = "Настройки IPv4 "
|
||||
"ipv4Configs" = "Настройки IPv4"
|
||||
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
||||
"xrayConfigTemplate" = "Шаблон конфигурации Xray"
|
||||
"xrayConfigTemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigTemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона."
|
||||
"xrayConfigFreedomStrategy" = "Настроить стратегию протокола Freedom"
|
||||
"xrayConfigFreedomStrategyDesc" = "Установить стратегию вывода сети в протоколе Freedom"
|
||||
"xrayConfigRoutingStrategy" = "Настроить доменную стратегию маршрутизации"
|
||||
"xrayConfigRoutingStrategyDesc" = "Установить общую стратегию маршрутизации разрешения DNS"
|
||||
"xrayConfigTorrent" = "Запретить использование BitTorrent"
|
||||
"xrayConfigTorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigTorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent."
|
||||
"xrayConfigPrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
|
||||
"xrayConfigPrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigAds" = "Бокировка рекламы"
|
||||
"xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigPrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов."
|
||||
"xrayConfigAds" = "Блокировка рекламы"
|
||||
"xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу."
|
||||
"xrayConfigFamily" = "Включить семейную конфигурацию"
|
||||
"xrayConfigFamilyDesc" = "Избегайте подключения к небезопасным веб-сайтам для всей семьи"
|
||||
"xrayConfigFamilyDesc" = "Избегать подключения к небезопасным веб-сайтам для всей семьи"
|
||||
"xrayConfigIRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
||||
"xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана."
|
||||
"xrayConfigIRDomain" = "Отключить подключение к доменам Ирана"
|
||||
"xrayConfigIRDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Ирана. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigIRDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Ирана."
|
||||
"xrayConfigChinaIp" = "Отключить подключение к диапазонам IP-адресов Китая"
|
||||
"xrayConfigChinaIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Китая. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigChinaIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Китая."
|
||||
"xrayConfigChinaDomain" = "Отключить подключение к доменам Китая"
|
||||
"xrayConfigChinaDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Китая. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigChinaDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Китая."
|
||||
"xrayConfigRussiaIp" = "Отключить подключение к диапазонам IP-адресов России"
|
||||
"xrayConfigRussiaIpDesc" = "Измените конфигурацию, чтобы отключить соединения с диапазонами IP-адресов России. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigRussiaIpDesc" = "Измените конфигурацию, чтобы отключить соединения с диапазонами IP-адресов России."
|
||||
"xrayConfigRussiaDomain" = "Отключить подключение к доменам России"
|
||||
"xrayConfigRussiaDomainDesc" = "Измените конфигурацию, чтобы избежать подключения к доменам России. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigRussiaDomainDesc" = "Измените конфигурацию, чтобы избежать подключения к доменам России."
|
||||
"xrayConfigDirectIRIp" = "Прямое подключение к диапазонам IP-адресов Ирана"
|
||||
"xrayConfigDirectIRIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Ирана"
|
||||
"xrayConfigDirectIRDomain" = "Прямое подключение к доменам Ирана"
|
||||
@@ -323,16 +324,16 @@
|
||||
"xrayConfigDirectRussiaDomain" = "Прямое подключение к доменам России"
|
||||
"xrayConfigDirectRussiaDomainDesc" = "Изменить шаблон конфигурации для прямого подключения к доменам России"
|
||||
"xrayConfigGoogleIPv4" = "Использовать IPv4 для Google"
|
||||
"xrayConfigGoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigGoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4."
|
||||
"xrayConfigNetflixIPv4" = "Использовать IPv4 для Netflix"
|
||||
"xrayConfigNetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigNetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4."
|
||||
"xrayConfigInbounds" = "Конфигурация подключений"
|
||||
"xrayConfigInboundsDesc" = "Изменение шаблона конфигурации, для подключения определенных пользователей. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigInboundsDesc" = "Изменение шаблона конфигурации, для подключения определенных пользователей."
|
||||
"xrayConfigOutbounds" = "Конфигурация исходящих"
|
||||
"xrayConfigOutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера. Перезагрузите панель для применения настроек"
|
||||
"xrayConfigOutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера."
|
||||
"xrayConfigRoutings" = "Настройка правил маршрутизации"
|
||||
"xrayConfigRoutingsDesc" = "Изменение шаблона конфигурации, для определения правил маршрутизации для этого сервера. Перезагрузите панель для применения настроек"
|
||||
"manualLists" = "ручные списки"
|
||||
"xrayConfigRoutingsDesc" = "Изменение шаблона конфигурации, для определения правил маршрутизации для этого сервера."
|
||||
"manualLists" = "Пользовательские списки"
|
||||
"manualListsDesc" = "Пожалуйста, используйте формат массива JSON"
|
||||
"manualBlockedIPs" = "Список заблокированных IP-адресов"
|
||||
"manualBlockedDomains" = "Список заблокированных доменов"
|
||||
@@ -350,12 +351,12 @@
|
||||
[tgbot]
|
||||
"noResult" = "❗ Нет результатов!"
|
||||
"wentWrong" = "❌ Что-то пошло не так!"
|
||||
"noInbounds" = "❗ Входящих соединений не найдено!"
|
||||
"noInbounds" = "❗ Подключений не найдено!"
|
||||
"unlimited" = "♾ Неограниченно"
|
||||
"day" = "День"
|
||||
"days" = "Дней"
|
||||
"unknown" = "Неизвестно"
|
||||
"inbounds" = "Входящие"
|
||||
"inbounds" = "Подключения"
|
||||
"clients" = "Клиенты"
|
||||
|
||||
[tgbot.commands]
|
||||
@@ -399,21 +400,21 @@
|
||||
"upload" = "🔼 Загрузка↑: {{ .Upload }}\r\n"
|
||||
"download" = "🔽 Скачивание↓: {{ .Download }}\r\n"
|
||||
"total" = "🔄 Всего: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||
"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n"
|
||||
"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n"
|
||||
"exhaustedMsg" = "🚨 Истекли {{ .Type }}:\r\n"
|
||||
"exhaustedCount" = "🚨 Количество истекших {{ .Type }}:\r\n"
|
||||
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
||||
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n \r\n"
|
||||
"depleteSoon" = "🔜 Скоро отключатся: {{ .Deplete }}\r\n \r\n"
|
||||
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
||||
|
||||
[tgbot.buttons]
|
||||
"dbBackup" = "Получить резервную копию DB"
|
||||
"dbBackup" = "Получить резервную копию базы данных"
|
||||
"serverUsage" = "Использование сервера"
|
||||
"getInbounds" = "Получить входящие потоки"
|
||||
"depleteSoon" = "Скоро исчерпание"
|
||||
"clientUsage" = "Получить использование"
|
||||
"getInbounds" = "Получить список подключений"
|
||||
"depleteSoon" = "Скоро отключатся"
|
||||
"clientUsage" = "Получить статистику"
|
||||
"commands" = "Команды"
|
||||
|
||||
[tgbot.answers]
|
||||
"getInboundsFailed" = "❌ Не удалось получить входящие потоки."
|
||||
"askToAddUser" = "Конфигурация не найдена!\r\nВы должны настроить свое телеграм-имя пользователя и попросить вашего администратора добавить его в вашу конфигурацию."
|
||||
"askToAddUserName" = "Конфигурация не найдена!\r\nПожалуйста, попросите вашего администратора использовать ваше телеграм-имя пользователя в вашей конфигурации(ях).\r\n\r\nВаше имя пользователя: <b>@{{ .TgUserName }}</b>"
|
||||
"getInboundsFailed" = "❌ Не удалось получить подключения."
|
||||
"askToAddUser" = "Конфигурация не найдена!\r\nВы должны настроить свое имя пользователя Telegram и попросить вашего администратора добавить его в вашу конфигурацию."
|
||||
"askToAddUserName" = "Конфигурация не найдена!\r\nПожалуйста, попросите вашего администратора использовать ваше имя пользователя Telegram в вашей конфигурации(ях).\r\n\r\nВаше имя пользователя: <b>@{{ .TgUserName }}</b>"
|
||||
|
||||
@@ -127,7 +127,6 @@
|
||||
"network" = "网络"
|
||||
"destinationPort" = "目标端口"
|
||||
"targetAddress" = "目标地址"
|
||||
"disableInsecureEncryption" = "禁用不安全加密"
|
||||
"monitorDesc" = "默认留空即可"
|
||||
"meansNoLimit" = "表示不限制"
|
||||
"totalFlow" = "总流量"
|
||||
@@ -268,6 +267,8 @@
|
||||
"subUpdatesDesc" = "客户端应用程序更新之间的间隔时间"
|
||||
"subEncrypt" = "加密配置"
|
||||
"subEncryptDesc" = "在订阅中加密返回的配置"
|
||||
"subShowInfo" = "显示使用信息"
|
||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "模板"
|
||||
|
||||
@@ -244,9 +244,6 @@ func (s *Server) startTask() {
|
||||
s.cron.AddJob("@every 10s", job.NewXrayTrafficJob())
|
||||
}()
|
||||
|
||||
// Check the inbound traffic every 30 seconds that the traffic exceeds and expires
|
||||
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
||||
|
||||
// Make a traffic condition every day, 8:30
|
||||
var entry cron.EntryID
|
||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||
|
||||
161
x-ui.sh
161
x-ui.sh
@@ -403,7 +403,139 @@ show_xray_status() {
|
||||
fi
|
||||
}
|
||||
|
||||
install_acme() {
|
||||
cd ~
|
||||
LOGI "install acme..."
|
||||
curl https://get.acme.sh | sh
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "install acme failed"
|
||||
return 1
|
||||
else
|
||||
LOGI "install acme succeed"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
ssl_cert_issue_main() {
|
||||
echo -e "${green}\t1.${plain} Get SSL"
|
||||
echo -e "${green}\t2.${plain} Revoke"
|
||||
echo -e "${green}\t3.${plain} Force Renew"
|
||||
read -p "Choose an option: " choice
|
||||
case "$choice" in
|
||||
1) ssl_cert_issue ;;
|
||||
2)
|
||||
local domain=""
|
||||
read -p "Please enter your domain name to revoke the certificate: " domain
|
||||
~/.acme.sh/acme.sh --revoke -d ${domain}
|
||||
LOGI "Certificate revoked"
|
||||
;;
|
||||
3)
|
||||
local domain=""
|
||||
read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain
|
||||
~/.acme.sh/acme.sh --renew -d ${domain} --force ;;
|
||||
*) echo "Invalid choice" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ssl_cert_issue() {
|
||||
# check for acme.sh first
|
||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
||||
echo "acme.sh could not be found. we will install it"
|
||||
install_acme
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "install acme failed, please check logs"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
# install socat second
|
||||
case "${release}" in
|
||||
ubuntu|debian)
|
||||
apt update && apt install socat -y ;;
|
||||
centos)
|
||||
yum -y update && yum -y install socat ;;
|
||||
fedora)
|
||||
dnf -y update && dnf -y install socat ;;
|
||||
*)
|
||||
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
||||
exit 1 ;;
|
||||
esac
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "install socat failed, please check logs"
|
||||
exit 1
|
||||
else
|
||||
LOGI "install socat succeed..."
|
||||
fi
|
||||
|
||||
# get the domain here,and we need verify it
|
||||
local domain=""
|
||||
read -p "Please enter your domain name:" domain
|
||||
LOGD "your domain is:${domain},check it..."
|
||||
# here we need to judge whether there exists cert already
|
||||
local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
|
||||
|
||||
if [ ${currentCert} == ${domain} ]; then
|
||||
local certInfo=$(~/.acme.sh/acme.sh --list)
|
||||
LOGE "system already has certs here,can not issue again,current certs details:"
|
||||
LOGI "$certInfo"
|
||||
exit 1
|
||||
else
|
||||
LOGI "your domain is ready for issuing cert now..."
|
||||
fi
|
||||
|
||||
# create a directory for install cert
|
||||
certPath="/root/cert/${domain}"
|
||||
if [ ! -d "$certPath" ]; then
|
||||
mkdir -p "$certPath"
|
||||
else
|
||||
rm -rf "$certPath"
|
||||
mkdir -p "$certPath"
|
||||
fi
|
||||
|
||||
# get needed port here
|
||||
local WebPort=80
|
||||
read -p "please choose which port do you use,default will be 80 port:" WebPort
|
||||
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
|
||||
LOGE "your input ${WebPort} is invalid,will use default port"
|
||||
fi
|
||||
LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..."
|
||||
# NOTE:This should be handled by user
|
||||
# open the port and kill the occupied progress
|
||||
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
||||
~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "issue certs failed,please check logs"
|
||||
rm -rf ~/.acme.sh/${domain}
|
||||
exit 1
|
||||
else
|
||||
LOGE "issue certs succeed,installing certs..."
|
||||
fi
|
||||
# install cert
|
||||
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
||||
--key-file /root/cert/${domain}/privkey.pem \
|
||||
--fullchain-file /root/cert/${domain}/fullchain.pem
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "install certs failed,exit"
|
||||
rm -rf ~/.acme.sh/${domain}
|
||||
exit 1
|
||||
else
|
||||
LOGI "install certs succeed,enable auto renew..."
|
||||
fi
|
||||
|
||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "auto renew failed, certs details:"
|
||||
ls -lah cert/*
|
||||
chmod 755 $certPath/*
|
||||
exit 1
|
||||
else
|
||||
LOGI "auto renew succeed, certs details:"
|
||||
ls -lah cert/*
|
||||
chmod 755 $certPath/*
|
||||
fi
|
||||
}
|
||||
|
||||
ssl_cert_issue_CF() {
|
||||
echo -E ""
|
||||
LOGD "******Instructions for use******"
|
||||
LOGI "This Acme script requires the following data:"
|
||||
@@ -413,12 +545,14 @@ ssl_cert_issue() {
|
||||
LOGI "4.The script applies for a certificate. The default installation path is /root/cert "
|
||||
confirm "Confirmed?[y/n]" "y"
|
||||
if [ $? -eq 0 ]; then
|
||||
cd ~
|
||||
LOGI "Install Acme-Script"
|
||||
curl https://get.acme.sh | sh
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Failed to install acme script"
|
||||
exit 1
|
||||
# check for acme.sh first
|
||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
||||
echo "acme.sh could not be found. we will install it"
|
||||
install_acme
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "install acme failed, please check logs"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
CF_Domain=""
|
||||
CF_GlobalKey=""
|
||||
@@ -489,7 +623,6 @@ show_usage() {
|
||||
echo "x-ui enable - Enable x-ui on system startup"
|
||||
echo "x-ui disable - Disable x-ui on system startup"
|
||||
echo "x-ui log - Check x-ui logs"
|
||||
echo "x-ui v2-ui - Migrate v2-ui Account data to x-ui"
|
||||
echo "x-ui update - Update x-ui"
|
||||
echo "x-ui install - Install x-ui"
|
||||
echo "x-ui uninstall - Uninstall x-ui"
|
||||
@@ -511,19 +644,20 @@ show_menu() {
|
||||
${green}7.${plain} View current panel settings
|
||||
————————————————
|
||||
${green}8.${plain} Start x-ui
|
||||
${green}9.${plain} stop x-ui
|
||||
${green}9.${plain} Stop x-ui
|
||||
${green}10.${plain} Reboot x-ui
|
||||
${green}11.${plain} Check x-ui state
|
||||
${green}12.${plain} Check x-ui logs
|
||||
————————————————
|
||||
${green}13.${plain} set x-ui Autostart
|
||||
${green}13.${plain} Set x-ui Autostart
|
||||
${green}14.${plain} Cancel x-ui Autostart
|
||||
————————————————
|
||||
${green}15.${plain} 一A key installation bbr (latest kernel)
|
||||
${green}16.${plain} 一Apply for a SSL certificate with one click(acme script)
|
||||
${green}16.${plain} 一SSL Certificate Management
|
||||
${green}17.${plain} 一Cloudflare SSL Certificate
|
||||
"
|
||||
show_status
|
||||
echo && read -p "Please enter your selection [0-16]: " num
|
||||
echo && read -p "Please enter your selection [0-17]: " num
|
||||
|
||||
case "${num}" in
|
||||
0)
|
||||
@@ -575,7 +709,10 @@ show_menu() {
|
||||
install_bbr
|
||||
;;
|
||||
16)
|
||||
ssl_cert_issue
|
||||
ssl_cert_issue_main
|
||||
;;
|
||||
17)
|
||||
ssl_cert_issue_CF
|
||||
;;
|
||||
*)
|
||||
LOGE "Please enter the correct number [0-16]"
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"x-ui/config"
|
||||
"x-ui/util/common"
|
||||
|
||||
@@ -58,16 +59,18 @@ type process struct {
|
||||
version string
|
||||
apiPort int
|
||||
|
||||
config *Config
|
||||
lines *queue.Queue
|
||||
exitErr error
|
||||
config *Config
|
||||
lines *queue.Queue
|
||||
exitErr error
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func newProcess(config *Config) *process {
|
||||
return &process{
|
||||
version: "Unknown",
|
||||
config: config,
|
||||
lines: queue.New(100),
|
||||
version: "Unknown",
|
||||
config: config,
|
||||
lines: queue.New(100),
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +114,10 @@ func (p *Process) GetConfig() *Config {
|
||||
return p.config
|
||||
}
|
||||
|
||||
func (p *Process) GetUptime() uint64 {
|
||||
return uint64(time.Since(p.startTime).Seconds())
|
||||
}
|
||||
|
||||
func (p *process) refreshAPIPort() {
|
||||
for _, inbound := range p.config.InboundConfigs {
|
||||
if inbound.Tag == "api" {
|
||||
|
||||
Reference in New Issue
Block a user