mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-19 15:25:49 +00:00
Compare commits
26 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 |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version: '1.20'
|
||||||
- name: build linux amd64 version
|
- name: build linux amd64 version
|
||||||
run: |
|
run: |
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
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
|
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/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.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
|
mv xray xray-linux-amd64
|
||||||
cd ..
|
cd ..
|
||||||
cd ..
|
cd ..
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
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/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.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
|
mv xray xray-linux-arm64
|
||||||
cd ..
|
cd ..
|
||||||
cd ..
|
cd ..
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
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/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.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
|
mv xray xray-linux-s390x
|
||||||
cd ..
|
cd ..
|
||||||
cd ..
|
cd ..
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
|
|||||||
mv xray "xray-linux-${FNAME}"
|
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/geoip.dat"
|
||||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.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"
|
||||||
cd ../../
|
cd ../../
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -217,20 +217,6 @@ Reference syntax:
|
|||||||
- Multi language bot
|
- Multi language bot
|
||||||
</details>
|
</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:
|
# T-Shoots:
|
||||||
|
|
||||||
**If you upgrade from an old version or other forks, for enable traffic for users you should do :**
|
**If you upgrade from an old version or other forks, for enable traffic for users you should do :**
|
||||||
@@ -281,6 +267,11 @@ restart panel
|
|||||||
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
||||||
- [MHSanaei](https://github.com/MHSanaei)
|
- [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
|
## Stargazers over time
|
||||||
|
|
||||||
[](https://starchart.cc/alireza0/x-ui)
|
[](https://starchart.cc/alireza0/x-ui)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.5.3
|
1.5.4
|
||||||
4
go.mod
4
go.mod
@@ -17,8 +17,8 @@ require (
|
|||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.12.0
|
golang.org/x/text v0.12.0
|
||||||
google.golang.org/grpc v1.57.0
|
google.golang.org/grpc v1.57.0
|
||||||
gorm.io/driver/sqlite v1.5.2
|
gorm.io/driver/sqlite v1.5.3
|
||||||
gorm.io/gorm v1.25.2
|
gorm.io/gorm v1.25.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
||||||
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
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 h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ install_x-ui() {
|
|||||||
echo -e "x-ui enable - Enable x-ui on system startup"
|
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 disable - Disable x-ui on system startup"
|
||||||
echo -e "x-ui log - Check x-ui logs"
|
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 update - Update x-ui"
|
||||||
echo -e "x-ui install - Install x-ui"
|
echo -e "x-ui install - Install x-ui"
|
||||||
echo -e "x-ui uninstall - Uninstall 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/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/sub"
|
"x-ui/sub"
|
||||||
"x-ui/v2ui"
|
|
||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
@@ -252,10 +251,6 @@ func main() {
|
|||||||
|
|
||||||
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
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)
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
var port int
|
var port int
|
||||||
var username string
|
var username string
|
||||||
@@ -282,7 +277,6 @@ func main() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" run run web panel")
|
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(" migrate migrate form other/old x-ui")
|
||||||
fmt.Println(" setting set settings")
|
fmt.Println(" setting set settings")
|
||||||
}
|
}
|
||||||
@@ -303,16 +297,6 @@ func main() {
|
|||||||
runWebServer()
|
runWebServer()
|
||||||
case "migrate":
|
case "migrate":
|
||||||
migrateDb()
|
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":
|
case "setting":
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -334,12 +318,10 @@ func main() {
|
|||||||
updateTgbotEnableSts(enabletgbot)
|
updateTgbotEnableSts(enabletgbot)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
fmt.Println("except 'run' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
runCmd.Usage()
|
runCmd.Usage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
v2uiCmd.Usage()
|
|
||||||
fmt.Println()
|
|
||||||
settingCmd.Usage()
|
settingCmd.Usage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
||||||
|
subShowInfo, _ := a.settingService.GetSubShowInfo()
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
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 {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
@@ -16,12 +18,14 @@ import (
|
|||||||
|
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
address string
|
address string
|
||||||
|
showInfo bool
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
settingServics service.SettingService
|
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.address = host
|
||||||
|
s.showInfo = showInfo
|
||||||
var result []string
|
var result []string
|
||||||
var headers []string
|
var headers []string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
@@ -139,10 +143,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
if inbound.Protocol != model.VMess {
|
if inbound.Protocol != model.VMess {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
|
||||||
obj := map[string]interface{}{
|
obj := map[string]interface{}{
|
||||||
"v": "2",
|
"v": "2",
|
||||||
"ps": remark,
|
|
||||||
"add": s.address,
|
"add": s.address,
|
||||||
"port": inbound.Port,
|
"port": inbound.Port,
|
||||||
"type": "none",
|
"type": "none",
|
||||||
@@ -241,7 +243,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
links := ""
|
links := ""
|
||||||
for index, d := range domains {
|
for index, d := range domains {
|
||||||
domain := d.(map[string]interface{})
|
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)
|
obj["add"] = domain["domain"].(string)
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
links += "\n"
|
links += "\n"
|
||||||
@@ -252,6 +254,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
return links
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj["ps"] = s.genRemark(inbound, email, "")
|
||||||
|
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
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
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
|
||||||
|
|
||||||
if len(domains) > 0 {
|
if len(domains) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
for index, d := range domains {
|
for index, d := range domains {
|
||||||
domain := d.(map[string]interface{})
|
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)
|
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
links += "\n"
|
links += "\n"
|
||||||
@@ -423,7 +426,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
return links
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
url.Fragment = remark
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
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
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
|
||||||
|
|
||||||
if len(domains) > 0 {
|
if len(domains) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
for index, d := range domains {
|
for index, d := range domains {
|
||||||
domain := d.(map[string]interface{})
|
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)
|
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
links += "\n"
|
links += "\n"
|
||||||
@@ -588,7 +589,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
return links
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
url.Fragment = remark
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
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
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, clients[clientIndex].Email)
|
|
||||||
url.Fragment = remark
|
|
||||||
return url.String()
|
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) {
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
switch val := data.(type) {
|
switch val := data.(type) {
|
||||||
case map[string]interface{}:
|
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-collapse-content,
|
||||||
.ant-card-dark .ant-calendar,
|
.ant-card-dark .ant-calendar,
|
||||||
.ant-card-dark .ant-table-placeholder,
|
.ant-card-dark .ant-table-placeholder,
|
||||||
|
.ant-card-dark .ant-select-selection__choice,
|
||||||
.ant-card-dark .ant-input-group-addon {
|
.ant-card-dark .ant-input-group-addon {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #262f3d;
|
background-color: #262f3d;
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ class AllSetting {
|
|||||||
this.subKeyFile = "";
|
this.subKeyFile = "";
|
||||||
this.subUpdates = 0;
|
this.subUpdates = 0;
|
||||||
this.subEncrypt = true;
|
this.subEncrypt = true;
|
||||||
|
this.subShowInfo = false;
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ const SSMethods = {
|
|||||||
AES_256_GCM: 'aes-256-gcm',
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
AES_128_GCM: 'aes-128-gcm',
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
|
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||||
|
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||||
@@ -35,7 +37,7 @@ const TLS_VERSION_OPTION = {
|
|||||||
TLS11: "1.1",
|
TLS11: "1.1",
|
||||||
TLS12: "1.2",
|
TLS12: "1.2",
|
||||||
TLS13: "1.3",
|
TLS13: "1.3",
|
||||||
}
|
};
|
||||||
|
|
||||||
const TLS_CIPHER_OPTION = {
|
const TLS_CIPHER_OPTION = {
|
||||||
RSA_AES_128_CBC: "TLS_RSA_WITH_AES_128_CBC_SHA",
|
RSA_AES_128_CBC: "TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||||
@@ -71,9 +73,9 @@ const UTLS_FINGERPRINT = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ALPN_OPTION = {
|
const ALPN_OPTION = {
|
||||||
HTTP1: "http/1.1",
|
|
||||||
H2: "h2",
|
|
||||||
H3: "h3",
|
H3: "h3",
|
||||||
|
H2: "h2",
|
||||||
|
HTTP1: "http/1.1",
|
||||||
};
|
};
|
||||||
|
|
||||||
const SNIFFING_OPTION = {
|
const SNIFFING_OPTION = {
|
||||||
@@ -459,8 +461,8 @@ class GrpcStreamSettings extends XrayCommonClass {
|
|||||||
|
|
||||||
class TlsStreamSettings extends XrayCommonClass {
|
class TlsStreamSettings extends XrayCommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
minVersion = TLS_VERSION_OPTION.TLS10,
|
minVersion = TLS_VERSION_OPTION.TLS12,
|
||||||
maxVersion = TLS_VERSION_OPTION.TLS12,
|
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
rejectUnknownSni = false,
|
rejectUnknownSni = false,
|
||||||
certificates=[new TlsStreamSettings.Cert()],
|
certificates=[new TlsStreamSettings.Cert()],
|
||||||
@@ -522,13 +524,14 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TlsStreamSettings.Cert = class extends XrayCommonClass {
|
TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') {
|
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='', ocspStapling=3600) {
|
||||||
super();
|
super();
|
||||||
this.useFile = useFile;
|
this.useFile = useFile;
|
||||||
this.certFile = certificateFile;
|
this.certFile = certificateFile;
|
||||||
this.keyFile = keyFile;
|
this.keyFile = keyFile;
|
||||||
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
|
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
|
||||||
this.key = key instanceof Array ? key.join('\n') : key;
|
this.key = key instanceof Array ? key.join('\n') : key;
|
||||||
|
this.ocspStapling = ocspStapling;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -536,13 +539,15 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
return new TlsStreamSettings.Cert(
|
return new TlsStreamSettings.Cert(
|
||||||
true,
|
true,
|
||||||
json.certificateFile,
|
json.certificateFile,
|
||||||
json.keyFile,
|
json.keyFile, '', '',
|
||||||
|
json.ocspStapling,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new TlsStreamSettings.Cert(
|
return new TlsStreamSettings.Cert(
|
||||||
false, '', '',
|
false, '', '',
|
||||||
json.certificate.join('\n'),
|
json.certificate.join('\n'),
|
||||||
json.key.join('\n'),
|
json.key.join('\n'),
|
||||||
|
json.ocspStapling,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,11 +557,13 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
return {
|
return {
|
||||||
certificateFile: this.certFile,
|
certificateFile: this.certFile,
|
||||||
keyFile: this.keyFile,
|
keyFile: this.keyFile,
|
||||||
|
ocspStapling: this.ocspStapling,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
certificate: this.cert.split('\n'),
|
certificate: this.cert.split('\n'),
|
||||||
key: this.key.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 {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
@@ -680,6 +716,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings=new HttpStreamSettings(),
|
httpSettings=new HttpStreamSettings(),
|
||||||
quicSettings=new QuicStreamSettings(),
|
quicSettings=new QuicStreamSettings(),
|
||||||
grpcSettings=new GrpcStreamSettings(),
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.network = network;
|
this.network = network;
|
||||||
@@ -692,6 +729,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
this.http = httpSettings;
|
this.http = httpSettings;
|
||||||
this.quic = quicSettings;
|
this.quic = quicSettings;
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTls() {
|
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={}) {
|
static fromJson(json={}) {
|
||||||
|
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
@@ -731,6 +777,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,6 +794,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.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) {
|
} else if (this.isWs) {
|
||||||
return this.stream.ws.path;
|
return this.stream.ws.path;
|
||||||
} else if (this.isH2) {
|
} else if (this.isH2) {
|
||||||
return this.stream.http.path[0];
|
return this.stream.http.path;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1411,10 +1459,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
JSON.parse(this.settings).clients.forEach((client,index) => {
|
JSON.parse(this.settings).clients.forEach((client,index) => {
|
||||||
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
||||||
this.stream.tls.settings.domains.forEach((domain) => {
|
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 {
|
} 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;
|
return link;
|
||||||
@@ -1492,11 +1540,9 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
|
|
||||||
Inbound.VmessSettings = class extends Inbound.Settings {
|
Inbound.VmessSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol,
|
constructor(protocol,
|
||||||
vmesses=[new Inbound.VmessSettings.Vmess()],
|
vmesses=[new Inbound.VmessSettings.Vmess()]) {
|
||||||
disableInsecureEncryption=false) {
|
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.vmesses = vmesses;
|
this.vmesses = vmesses;
|
||||||
this.disableInsecure = disableInsecureEncryption;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
indexOfVmessById(id) {
|
indexOfVmessById(id) {
|
||||||
@@ -1521,19 +1567,17 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
return new Inbound.VmessSettings(
|
return new Inbound.VmessSettings(
|
||||||
Protocols.VMESS,
|
Protocols.VMESS,
|
||||||
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
|
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
|
||||||
ObjectUtil.isEmpty(json.disableInsecureEncryption) ? false : json.disableInsecureEncryption,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
||||||
disableInsecureEncryption: this.disableInsecure,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
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();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
@@ -1621,7 +1665,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
|
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
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();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -1742,7 +1786,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
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();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
@@ -1878,7 +1922,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
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();
|
super();
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
|||||||
@@ -75,17 +75,7 @@ class PromiseUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const seq = [
|
const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||||
'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'
|
|
||||||
];
|
|
||||||
|
|
||||||
class RandomUtil {
|
class RandomUtil {
|
||||||
static randomIntRange(min, max) {
|
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() {
|
static randomShadowsocksPassword() {
|
||||||
let array = new Uint8Array(32);
|
let array = new Uint8Array(32);
|
||||||
window.crypto.getRandomValues(array);
|
window.crypto.getRandomValues(array);
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
|||||||
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
||||||
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
||||||
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
|
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
|
||||||
|
"subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() },
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
result := make(map[string]interface{})
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ type AllSetting struct {
|
|||||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
|
|||||||
@@ -35,15 +35,16 @@
|
|||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
settings = JSON.parse(this.inbound.settings);
|
settings = JSON.parse(this.inbound.settings);
|
||||||
this.client = settings.clients[clientIndex];
|
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;
|
address = this.dbInbound.address;
|
||||||
this.subId = '';
|
this.subId = '';
|
||||||
this.qrcodes = [];
|
this.qrcodes = [];
|
||||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
||||||
this.qrcodes.push({
|
this.qrcodes.push({
|
||||||
remark: remark + "-" + domain.remark,
|
remark: remarkText,
|
||||||
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, clientIndex)
|
link: this.inbound.genLink(domain.domain, remarkText, clientIndex)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -84,7 +85,7 @@
|
|||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
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() {
|
updated() {
|
||||||
|
|||||||
@@ -119,15 +119,6 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
resetClientTraffic(email,dbInboundId,iconElement) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
<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>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
<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>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
<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>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/vless"}}
|
{{define "form/vless"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
<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>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/vmess"}}
|
{{define "form/vmess"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
<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>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||||
@@ -110,9 +110,4 @@
|
|||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</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}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{{define "form/sniffing"}}
|
{{define "form/sniffing"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<a-divider dashed style="margin:0;">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
sniffing
|
sniffing
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
</a-divider>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
<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>
|
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<a-divider dashed style="margin:0;">
|
||||||
<a-form-item label="{{ i18n "transmission" }}">
|
<a-form-item label="{{ i18n "transmission" }}">
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
<a-select-option value="grpc">grpc</a-select-option>
|
<a-select-option value="grpc">grpc</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
</a-divider>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tcp -->
|
<!-- tcp -->
|
||||||
@@ -43,4 +45,8 @@
|
|||||||
<template v-if="inbound.stream.network === 'grpc'">
|
<template v-if="inbound.stream.network === 'grpc'">
|
||||||
{{template "form/streamGRPC"}}
|
{{template "form/streamGRPC"}}
|
||||||
</template>
|
</template>
|
||||||
|
<!-- sockopt -->
|
||||||
|
<template>
|
||||||
|
{{template "form/streamSockopt"}}
|
||||||
|
</template>
|
||||||
{{end}}
|
{{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"}}
|
{{define "form/tlsSettings"}}
|
||||||
<!-- tls enable -->
|
<!-- tls enable -->
|
||||||
<a-form v-if="inbound.canSetTls()" layout="inline">
|
<a-form v-if="inbound.canSetTls()" layout="inline">
|
||||||
|
<a-divider dashed style="margin:0;">
|
||||||
<a-form-item label="TLS">
|
<a-form-item label="TLS">
|
||||||
<a-switch v-model="inbound.tls">
|
<a-switch v-model="inbound.tls">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
@@ -8,6 +9,7 @@
|
|||||||
<a-form-item v-if="inbound.canEnableReality()" label="Reality">
|
<a-form-item v-if="inbound.canEnableReality()" label="Reality">
|
||||||
<a-switch v-model="inbound.reality"></a-switch>
|
<a-switch v-model="inbound.reality"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
</a-divider>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
@@ -94,12 +96,16 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alpn</td>
|
<td>ALPN</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-checkbox-group v-model="inbound.stream.tls.alpn">
|
<a-select
|
||||||
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
mode="multiple"
|
||||||
</a-checkbox-group>
|
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>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -174,6 +180,14 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</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>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -260,14 +260,15 @@
|
|||||||
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
||||||
this.isExpired = this.inbound.isExpiry(index);
|
this.isExpired = this.inbound.isExpiry(index);
|
||||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
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;
|
address = this.dbInbound.address;
|
||||||
this.links = [];
|
this.links = [];
|
||||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
||||||
this.links.push({
|
this.links.push({
|
||||||
remark: remark + "-" + domain.remark,
|
remark: remarkText,
|
||||||
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, index)
|
link: this.inbound.genLink(domain.domain, remarkText, index)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -291,7 +292,7 @@
|
|||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
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){
|
if (this.inModal.inbound.settings.shadowsockses.length ==0){
|
||||||
this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
|
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 => {
|
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||||
client.method = this.inModal.inbound.settings.method;
|
client.method = this.inModal.inbound.settings.method;
|
||||||
})
|
})
|
||||||
@@ -139,15 +139,6 @@
|
|||||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
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;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -726,8 +726,7 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
findIndexOfClient(clients, client) {
|
findIndexOfClient(clients, client) {
|
||||||
firstKey = Object.keys(client)[0];
|
return clients.findIndex(item => JSON.stringify(item) === JSON.stringify(client));
|
||||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
|
||||||
},
|
},
|
||||||
async addClient(clients, dbInboundId) {
|
async addClient(clients, dbInboundId) {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -833,6 +832,7 @@
|
|||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
inbound = dbInbound.toInbound();
|
inbound = dbInbound.toInbound();
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
|
client.enable = !client.enable; // For finding correct index in findIndexOfClient() function
|
||||||
index = this.findIndexOfClient(clients, client);
|
index = this.findIndexOfClient(clients, client);
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||||
|
|||||||
@@ -295,7 +295,7 @@
|
|||||||
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
||||||
[[ backupModal.description ]]
|
[[ backupModal.description ]]
|
||||||
</p>
|
</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()">
|
<a-button type="primary" @click="exportDatabase()">
|
||||||
[[ backupModal.exportText ]]
|
[[ backupModal.exportText ]]
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|||||||
@@ -338,6 +338,7 @@
|
|||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
<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.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.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.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="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>
|
<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)
|
logger.Warning("get xray traffic failed:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = j.inboundService.AddTraffic(traffics)
|
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("add traffic failed:", err)
|
logger.Warning("add traffic failed:", err)
|
||||||
}
|
}
|
||||||
|
if needRestart {
|
||||||
err = j.inboundService.AddClientTraffic(clientTraffics)
|
j.xrayService.SetToNeedRestart()
|
||||||
if err != nil {
|
|
||||||
logger.Warning("add client traffic failed:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,38 +194,6 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
|||||||
return inbound, needRestart, err
|
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) {
|
func (s *InboundService) DelInbound(id int) (bool, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -659,35 +627,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
return needRestart, tx.Save(oldInbound).Error
|
return needRestart, tx.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
|
func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||||
if len(traffics) == 0 {
|
var err error
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
|
||||||
@@ -698,13 +639,64 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
tx.Commit()
|
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))
|
emails := make([]string, 0, len(traffics))
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
emails = append(emails, traffic.Email)
|
emails = append(emails, traffic.Email)
|
||||||
}
|
}
|
||||||
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -789,14 +781,13 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
|||||||
return dbClientTraffics, nil
|
return dbClientTraffics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
||||||
db := database.GetDB()
|
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
needRestart := false
|
needRestart := false
|
||||||
|
|
||||||
if p != nil {
|
if p != nil {
|
||||||
var tags []string
|
var tags []string
|
||||||
err := db.Table("inbounds").
|
err := tx.Table("inbounds").
|
||||||
Select("inbounds.tag").
|
Select("inbounds.tag").
|
||||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||||
Scan(&tags).Error
|
Scan(&tags).Error
|
||||||
@@ -816,7 +807,7 @@ func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
|||||||
s.xrayApi.Close()
|
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).
|
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||||
Update("enable", false)
|
Update("enable", false)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
@@ -824,8 +815,7 @@ func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
|||||||
return needRestart, count, err
|
return needRestart, count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) {
|
||||||
db := database.GetDB()
|
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
needRestart := false
|
needRestart := false
|
||||||
|
|
||||||
@@ -835,7 +825,7 @@ func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
|||||||
Email string
|
Email string
|
||||||
}
|
}
|
||||||
|
|
||||||
err := db.Table("inbounds").
|
err := tx.Table("inbounds").
|
||||||
Select("inbounds.tag, client_traffics.email").
|
Select("inbounds.tag, client_traffics.email").
|
||||||
Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id").
|
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).
|
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()
|
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).
|
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||||
Update("enable", false)
|
Update("enable", false)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"subKeyFile": "",
|
"subKeyFile": "",
|
||||||
"subUpdates": "12",
|
"subUpdates": "12",
|
||||||
"subEncrypt": "true",
|
"subEncrypt": "true",
|
||||||
|
"subShowInfo": "false",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -377,6 +378,10 @@ func (s *SettingService) GetSubEncrypt() (bool, error) {
|
|||||||
return s.getBool("subEncrypt")
|
return s.getBool("subEncrypt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubShowInfo() (bool, error) {
|
||||||
|
return s.getBool("subShowInfo")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||||
if err := allSetting.CheckValid(); err != nil {
|
if err := allSetting.CheckValid(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.inboundService.DisableInvalidClients()
|
s.inboundService.AddTraffic(nil, nil)
|
||||||
|
|
||||||
inbounds, err := s.inboundService.GetAllInbounds()
|
inbounds, err := s.inboundService.GetAllInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -127,7 +127,6 @@
|
|||||||
"network" = "Network"
|
"network" = "Network"
|
||||||
"destinationPort" = "Destination Port"
|
"destinationPort" = "Destination Port"
|
||||||
"targetAddress" = "Target Address"
|
"targetAddress" = "Target Address"
|
||||||
"disableInsecureEncryption" = "Disable Insecure Encryption"
|
|
||||||
"monitorDesc" = "Leave blank by default"
|
"monitorDesc" = "Leave blank by default"
|
||||||
"meansNoLimit" = "Means No Limit"
|
"meansNoLimit" = "Means No Limit"
|
||||||
"totalFlow" = "Total Flow"
|
"totalFlow" = "Total Flow"
|
||||||
@@ -268,7 +267,8 @@
|
|||||||
"subUpdatesDesc" = "Interval hours between updates in client application"
|
"subUpdatesDesc" = "Interval hours between updates in client application"
|
||||||
"subEncrypt" = "Encrypt configs"
|
"subEncrypt" = "Encrypt configs"
|
||||||
"subEncryptDesc" = "Encrypt the returned configs in subscription"
|
"subEncryptDesc" = "Encrypt the returned configs in subscription"
|
||||||
|
"subShowInfo" = "Show usage info"
|
||||||
|
"subShowInfoDesc" = "Show remianed traffic and date after config name"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "Templates"
|
"title" = "Templates"
|
||||||
|
|||||||
@@ -127,7 +127,6 @@
|
|||||||
"network" = "شبکه"
|
"network" = "شبکه"
|
||||||
"destinationPort" = "پورت مقصد"
|
"destinationPort" = "پورت مقصد"
|
||||||
"targetAddress" = "آدرس مقصد"
|
"targetAddress" = "آدرس مقصد"
|
||||||
"disableInsecureEncryption" = "غیرفعال سازی رمزگذاری ناامن"
|
|
||||||
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
||||||
"meansNoLimit" = "یعنی بدون محدودیت"
|
"meansNoLimit" = "یعنی بدون محدودیت"
|
||||||
"totalFlow" = "کل ترافیک"
|
"totalFlow" = "کل ترافیک"
|
||||||
@@ -267,6 +266,8 @@
|
|||||||
"subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر"
|
"subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر"
|
||||||
"subEncrypt" = "رمزگذاری کانفیگ ها"
|
"subEncrypt" = "رمزگذاری کانفیگ ها"
|
||||||
"subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن"
|
"subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن"
|
||||||
|
"subShowInfo" = "نمایش اطلاعات مصرف"
|
||||||
|
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "الگوها"
|
"title" = "الگوها"
|
||||||
|
|||||||
@@ -1,94 +1,94 @@
|
|||||||
"username" = "имя пользователя"
|
"username" = "Имя пользователя"
|
||||||
"password" = "пароль"
|
"password" = "Пароль"
|
||||||
"login" = "логин"
|
"login" = "Войти"
|
||||||
"confirm" = "подтвердить"
|
"confirm" = "Подтвердить"
|
||||||
"cancel" = "отмена"
|
"cancel" = "Отмена"
|
||||||
"close" = "закрыть"
|
"close" = "Закрыть"
|
||||||
"copy" = "копировать"
|
"copy" = "Копировать"
|
||||||
"copied" = "скопировано"
|
"copied" = "Скопировано"
|
||||||
"download" = "скачать"
|
"download" = "Скачать"
|
||||||
"remark" = "примечание"
|
"remark" = "Примечание"
|
||||||
"enable" = "включить"
|
"enable" = "Включить"
|
||||||
"protocol" = "протокол"
|
"protocol" = "Протокол"
|
||||||
"search" = "поиск"
|
"search" = "Поиск"
|
||||||
"filter" = "Фильтр"
|
"filter" = "Фильтр"
|
||||||
"loading" = "загрузка"
|
"loading" = "Загрузка"
|
||||||
"second" = "секунда"
|
"second" = "Секунда"
|
||||||
"minute" = "минута"
|
"minute" = "Минута"
|
||||||
"hour" = "час"
|
"hour" = "Час"
|
||||||
"day" = "день"
|
"day" = "День"
|
||||||
"check" = "просмотр"
|
"check" = "просмотр"
|
||||||
"indefinite" = "бессрочно"
|
"indefinite" = "Бессрочно"
|
||||||
"unlimited" = "безлимитно"
|
"unlimited" = "Безлимитно"
|
||||||
"none" = "пусто"
|
"none" = "Пусто"
|
||||||
"qrCode" = "QR-код"
|
"qrCode" = "QR-код"
|
||||||
"info" = "больше информации"
|
"info" = "Больше информации"
|
||||||
"edit" = "изменить"
|
"edit" = "Изменить"
|
||||||
"delete" = "удалить"
|
"delete" = "Удалить"
|
||||||
"reset" = "обнулить"
|
"reset" = "Обнулить"
|
||||||
"copySuccess" = "скопировано"
|
"copySuccess" = "Успешно скопировано"
|
||||||
"sure" = "да"
|
"sure" = "Да"
|
||||||
"encryption" = "Шифрование"
|
"encryption" = "Шифрование"
|
||||||
"transmission" = "протокол передачи"
|
"transmission" = "Протокол передачи"
|
||||||
"host" = "хост"
|
"host" = "Хост"
|
||||||
"path" = "путь"
|
"path" = "Путь"
|
||||||
"camouflage" = "маскировка"
|
"camouflage" = "Маскировка"
|
||||||
"status" = "статус"
|
"status" = "Статус"
|
||||||
"enabled" = "включено"
|
"enabled" = "Включено"
|
||||||
"disabled" = "отключено"
|
"disabled" = "Отключено"
|
||||||
"depleted" = "исчерпано"
|
"depleted" = "Отключены"
|
||||||
"depletingSoon" = "почти исчерпано"
|
"depletingSoon" = "Почти отключены"
|
||||||
"domainName" = "домен"
|
"domainName" = "Домен"
|
||||||
"monitor" = "порт IP"
|
"monitor" = "Прослушиваемый IP"
|
||||||
"certificate" = "сертификат"
|
"certificate" = "Сертификат"
|
||||||
"fail" = "неудача"
|
"fail" = "Неудачно"
|
||||||
"success" = "успешно"
|
"success" = "Успешно"
|
||||||
"getVersion" = "узнать версию"
|
"getVersion" = "Узнать версию"
|
||||||
"install" = "установка"
|
"install" = "установка"
|
||||||
"clients" = "клиенты"
|
"clients" = "Клиенты"
|
||||||
"usage" = "использование"
|
"usage" = "Использовано"
|
||||||
"remained" = "остались"
|
"remained" = "Осталось"
|
||||||
"secAlertTitle" = "Предупреждение системы безопасности"
|
"secAlertTitle" = "Предупреждение системы безопасности"
|
||||||
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации, пока TLS не будет активирован для защиты данных"
|
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "статус системы"
|
"dashboard" = "Статус системы"
|
||||||
"inbounds" = "пользователи"
|
"inbounds" = "Подключения"
|
||||||
"settings" = "настройки"
|
"settings" = "Настройки"
|
||||||
"logout" = "выход"
|
"logout" = "Выйти"
|
||||||
"link" = "другое"
|
"link" = "Другое"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
"title" = "логин"
|
"title" = "Войти"
|
||||||
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
|
"loginAgain" = "Время сессии истекло. Пожалуйста, войдите в систему снова"
|
||||||
|
|
||||||
[pages.login.toasts]
|
[pages.login.toasts]
|
||||||
"invalidFormData" = "Недопустимый формат данных"
|
"invalidFormData" = "Недопустимый формат данных"
|
||||||
"emptyUsername" = "Введите имя пользователя"
|
"emptyUsername" = "Введите имя пользователя"
|
||||||
"emptyPassword" = "Введите пароль"
|
"emptyPassword" = "Введите пароль"
|
||||||
"wrongUsernameOrPassword" = "Неверное имя пользователя или пароль"
|
"wrongUsernameOrPassword" = "Неверное имя пользователя или пароль"
|
||||||
"successLogin" = "успешный вход"
|
"successLogin" = "Успешный вход"
|
||||||
|
|
||||||
[pages.index]
|
[pages.index]
|
||||||
"title" = "статус системы"
|
"title" = "Статус системы"
|
||||||
"memory" = "память"
|
"memory" = "ОЗУ"
|
||||||
"hard" = "жесткий диск"
|
"hard" = "Место на диске"
|
||||||
"xrayStatus" = "статус Xray"
|
"xrayStatus" = "Статус Xray"
|
||||||
"stopXray" = "стоп"
|
"stopXray" = "Остановка"
|
||||||
"restartXray" = "рестарт Xray"
|
"restartXray" = "Перезапуск Xray"
|
||||||
"xraySwitch" = "переключить версию"
|
"xraySwitch" = "Сменить версию"
|
||||||
"xraySwitchClick" = "Выберите желаемую версию"
|
"xraySwitchClick" = "Выберите желаемую версию"
|
||||||
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями"
|
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями"
|
||||||
"operationHours" = "Часы работы"
|
"operationHours" = "Время работы"
|
||||||
"operationHoursDesc" = "Аптайм системы: время системы в сети"
|
"operationHoursDesc" = "Время работы системы: время с момента запуска."
|
||||||
"systemLoad" = "Системная нагрузка"
|
"systemLoad" = "Системная нагрузка"
|
||||||
"connectionCount" = "количество соединений"
|
"connectionCount" = "Количество соединений"
|
||||||
"connectionCountDesc" = "Всего подключений по всем сетям»"
|
"connectionCountDesc" = "Всего подключений по всем сетям»"
|
||||||
"upSpeed" = "Общая скорость upload"
|
"upSpeed" = "Общая скорость отдачи"
|
||||||
"downSpeed" = "Общая скорость download"
|
"downSpeed" = "Общая скорость получения"
|
||||||
"totalSent" = "Общий объем загруженных данных с момента запуска системы"
|
"totalSent" = "Общий объем загруженных данных с момента запуска системы"
|
||||||
"totalReceive" = "Общий объем полученных данных с момента запуска системы"
|
"totalReceive" = "Общий объем полученных данных с момента запуска системы"
|
||||||
"xraySwitchVersionDialog" = "переключить версию Xray"
|
"xraySwitchVersionDialog" = "Переключить версию Xray"
|
||||||
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
|
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
|
||||||
"dontRefresh" = "Установка. Не обновляйте эту страницу"
|
"dontRefresh" = "Установка. Не обновляйте эту страницу"
|
||||||
"logs" = "Логи"
|
"logs" = "Логи"
|
||||||
@@ -100,39 +100,38 @@
|
|||||||
"importDatabase" = "Импорт базы данных"
|
"importDatabase" = "Импорт базы данных"
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "пользователи"
|
"title" = "Подключения"
|
||||||
"totalDownUp" = "Всего входящих/исходящих"
|
"totalDownUp" = "Всего получено/отправлено"
|
||||||
"totalUsage" = "Всего использовано"
|
"totalUsage" = "Всего использовано"
|
||||||
"inboundCount" = "Количество пользователей"
|
"inboundCount" = "Количество подключений"
|
||||||
"operate" = "Меню"
|
"operate" = "Меню"
|
||||||
"enable" = "Включить"
|
"enable" = "Включить"
|
||||||
"remark" = "Примечание"
|
"remark" = "Примечание"
|
||||||
"protocol" = "Протокол"
|
"protocol" = "Протокол"
|
||||||
"port" = "Порт"
|
"port" = "Порт"
|
||||||
"traffic" = "Траффик"
|
"traffic" = "Трафик"
|
||||||
"details" = "Подробнее"
|
"details" = "Подробнее"
|
||||||
"transportConfig" = "Перенести"
|
"transportConfig" = "Перенести"
|
||||||
"expireDate" = "Дата окончания"
|
"expireDate" = "Дата окончания"
|
||||||
"resetTraffic" = "Обнулить траффик"
|
"resetTraffic" = "Обнулить трафик"
|
||||||
"addInbound" = "Добавить пользователя"
|
"addInbound" = "Добавить подключение"
|
||||||
"generalActions" = "Общие действия"
|
"generalActions" = "Общие действия"
|
||||||
"create" = "Создать"
|
"create" = "Создать"
|
||||||
"update" = "Обновить"
|
"update" = "Обновить"
|
||||||
"modifyInbound" = "Изменить данные"
|
"modifyInbound" = "Изменить данные"
|
||||||
"deleteInbound" = "Удалить пользователя"
|
"deleteInbound" = "Удалить подключение"
|
||||||
"deleteInboundContent" = "Подтвердите удаление пользователя?"
|
"deleteInboundContent" = "Вы уверены, что хотите удалить подключение?"
|
||||||
"resetTrafficContent" = "Подтвердите обнуление траффика?"
|
"resetTrafficContent" = "Подтвердите обнуление трафика?"
|
||||||
"copyLink" = "Копировать ключ"
|
"copyLink" = "Копировать ключ"
|
||||||
"address" = "Адрес"
|
"address" = "Адрес"
|
||||||
"network" = "Сеть"
|
"network" = "Сеть"
|
||||||
"destinationPort" = "Порт назначения"
|
"destinationPort" = "Порт назначения"
|
||||||
"targetAddress" = "Целевой адрес"
|
"targetAddress" = "Целевой адрес"
|
||||||
"disableInsecureEncryption" = "Отключить небезопасное шифрование"
|
|
||||||
"monitorDesc" = "Оставьте пустым по умолчанию"
|
"monitorDesc" = "Оставьте пустым по умолчанию"
|
||||||
"meansNoLimit" = "Значит без ограничений"
|
"meansNoLimit" = "Значит без ограничений"
|
||||||
"totalFlow" = "Общий расход"
|
"totalFlow" = "Общий расход"
|
||||||
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы никогда не истекать"
|
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы сделать бессрочно"
|
||||||
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
"noRecommendKeepDefault" = "Нет особых требований для сохранения настроек по умолчанию"
|
||||||
"certificatePath" = "Путь файла сертификата"
|
"certificatePath" = "Путь файла сертификата"
|
||||||
"certificateContent" = "Содержимое файла сертификата"
|
"certificateContent" = "Содержимое файла сертификата"
|
||||||
"publicKeyPath" = "Путь к публичному ключу"
|
"publicKeyPath" = "Путь к публичному ключу"
|
||||||
@@ -143,21 +142,21 @@
|
|||||||
"client" = "Клиент"
|
"client" = "Клиент"
|
||||||
"export" = "Поделиться ключом"
|
"export" = "Поделиться ключом"
|
||||||
"clone" = "Клонировать"
|
"clone" = "Клонировать"
|
||||||
"cloneInbound" = "Клонировать пользователя"
|
"cloneInbound" = "Клонировать подключение"
|
||||||
"cloneInboundContent" = "Все настройки этого пользователя, кроме порта, порт прослушки и клиентов, будут клонированы"
|
"cloneInboundContent" = "Все настройки этого подключения, кроме порта, порт прослушки и клиентов, будут клонированы"
|
||||||
"cloneInboundOk" = "Клонировать"
|
"cloneInboundOk" = "Клонировать"
|
||||||
"resetAllTraffic" = "Обнулить весь траффик"
|
"resetAllTraffic" = "Обнулить весь трафик"
|
||||||
"resetAllTrafficTitle" = "Обнуление всего траффика"
|
"resetAllTrafficTitle" = "Обнуление всего трафика"
|
||||||
"resetAllTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
|
"resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех подключений?"
|
||||||
"resetInboundClientTraffics" = "Обнулить траффик пользователей"
|
"resetInboundClientTraffics" = "Обнулить трафик клиентов"
|
||||||
"resetInboundClientTrafficTitle" = "Обнуление траффика пользователей"
|
"resetInboundClientTrafficTitle" = "Обнуление трафика клиентов"
|
||||||
"resetInboundClientTrafficContent" = "Вы уверены, что хотите обнулить весь трафик для этих пользователей?"
|
"resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить весь трафик для клиентов этого подключения?"
|
||||||
"resetAllClientTraffics" = "Обнулить весь траффик пользователей"
|
"resetAllClientTraffics" = "Обнулить весь трафик клиентов"
|
||||||
"resetAllClientTrafficTitle" = "Обнуление всего траффика пользователей"
|
"resetAllClientTrafficTitle" = "Обнуление всего трафика клиентов"
|
||||||
"resetAllClientTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
|
"resetAllClientTrafficContent" = "Вы уверены, что хотите сбросить трафик для всех клиентов?"
|
||||||
"delDepletedClients" = "Удалить отключенных пользователей"
|
"delDepletedClients" = "Удалить отключенных клиентов"
|
||||||
"delDepletedClientsTitle" = "Удаление отключенных пользователей"
|
"delDepletedClientsTitle" = "Удаление отключенных клиентов"
|
||||||
"delDepletedClientsContent" = "Подтверждаете удаление отключенных пользователей?"
|
"delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных клиентов?"
|
||||||
"email" = "Email"
|
"email" = "Email"
|
||||||
"emailDesc" = "Пожалуйста, укажите уникальный Email"
|
"emailDesc" = "Пожалуйста, укажите уникальный Email"
|
||||||
"setDefaultCert" = "Установить сертификат с панели"
|
"setDefaultCert" = "Установить сертификат с панели"
|
||||||
@@ -165,12 +164,12 @@
|
|||||||
"subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигов"
|
"subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигов"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Добавить пользователя"
|
"add" = "Добавить клиента"
|
||||||
"edit" = "Редактировать пользователя"
|
"edit" = "Редактировать клиента"
|
||||||
"submitAdd" = "Добавить пользователя"
|
"submitAdd" = "Добавить клиента"
|
||||||
"submitEdit" = "Сохранить изменения"
|
"submitEdit" = "Сохранить изменения"
|
||||||
"clientCount" = "Количество пользователей"
|
"clientCount" = "Количество клиентов"
|
||||||
"bulk" = "Добавить несколько"
|
"bulk" = "Добавить несколько клиентов"
|
||||||
"method" = "Метод"
|
"method" = "Метод"
|
||||||
"first" = "Первый"
|
"first" = "Первый"
|
||||||
"last" = "Последний"
|
"last" = "Последний"
|
||||||
@@ -184,18 +183,18 @@
|
|||||||
"obtain" = "Получить"
|
"obtain" = "Получить"
|
||||||
|
|
||||||
[pages.inbounds.stream.general]
|
[pages.inbounds.stream.general]
|
||||||
"requestHeader" = "Требуется заголовок"
|
"requestHeader" = "Заголовок запроса"
|
||||||
"name" = "Имя"
|
"name" = "Имя"
|
||||||
"value" = "Значение"
|
"value" = "Значение"
|
||||||
|
|
||||||
[pages.inbounds.stream.tcp]
|
[pages.inbounds.stream.tcp]
|
||||||
"requestVersion" = "Требуется версия"
|
"requestVersion" = "Версия запроса"
|
||||||
"requestMethod" = "Требуется метод"
|
"requestMethod" = "Метод запроса"
|
||||||
"requestPath" = "Требуется путь"
|
"requestPath" = "Петь запроса"
|
||||||
"responseVersion" = "Указать версию"
|
"responseVersion" = "Версия ответа"
|
||||||
"responseStatus" = "Указать статус"
|
"responseStatus" = "Статус ответа"
|
||||||
"responseStatusDescription" = "Указать примечание статуса"
|
"responseStatusDescription" = "Описание статуса ответа"
|
||||||
"responseHeader" = "Указать заголовок"
|
"responseHeader" = "Заголовок ответа"
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
[pages.inbounds.stream.quic]
|
||||||
"encryption" = "Шифрование"
|
"encryption" = "Шифрование"
|
||||||
@@ -203,113 +202,115 @@
|
|||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "Настройки"
|
"title" = "Настройки"
|
||||||
"save" = "Сохранить"
|
"save" = "Сохранить"
|
||||||
"infoDesc" = "Каждое изменение здесь необходимо сохранить и перезапустить панель, чтобы оно вступило в силу"
|
"infoDesc" = "Все внесенные здесь изменения должны быть сохранены. Чтобы изменения вступили в силу, перезапустите панель."
|
||||||
"restartPanel" = "Рестарт панели"
|
"restartPanel" = "Перезапуск панели"
|
||||||
"restartPanelDesc" = "Подтвердите рестарт панели? ОК для рестарта панели через 3 сек. Если вы не можете пользоваться панелью после рестарта, пожалуйста, посмотрите лог панели на сервере"
|
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Нажмите OK для перезапуска через 3 секунды. Если после перезапуска не удается получить доступ к панели, просмотрите информацию журнала панели на сервере."
|
||||||
"resetDefaultConfig" = "Сбросить всё по-умолчанию"
|
"resetDefaultConfig" = "Сбросить всё по-умолчанию"
|
||||||
"panelConfig" = "Настройки панели"
|
"panelConfig" = "Настройки панели"
|
||||||
"userSettings" = "Настройки безопасности"
|
"userSettings" = "Настройки безопасности"
|
||||||
"xrayConfiguration" = "Конфигурация Xray"
|
"xrayConfiguration" = "Конфигурация Xray"
|
||||||
"TGBotSettings" = "Настройки Телеграм-бота"
|
"TGBotSettings" = "Настройки Телеграм-бота"
|
||||||
"panelListeningIP" = "IP-порт панели"
|
"panelListeningIP" = "IP-адрес прослушивания панели"
|
||||||
"panelListeningIPDesc" = "Оставьте пустым для работы с любого IP. Перезагрузите панель для применения настроек"
|
"panelListeningIPDesc" = "Оставьте пустым, чтобы прослушивать все IP-адреса."
|
||||||
"panelListeningDomain" = "Домен прослушивания панели"
|
"panelListeningDomain" = "Домен прослушивания панели"
|
||||||
"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса"
|
"panelListeningDomainDesc" = "Оставьте пустым, чтобы прослушивать все домены и IP-адреса"
|
||||||
"panelPort" = "Порт панели"
|
"panelPort" = "Порт панели"
|
||||||
"panelPortDesc" = "Перезагрузите панель для применения настроек"
|
"panelPortDesc" = "Номер порта для доступа к панели"
|
||||||
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
|
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
|
||||||
"publicKeyPathDesc" = "Введите полный путь, начинающийся с «/». Перезагрузите панель для применения настроек"
|
"publicKeyPathDesc" = "Введите полный путь, начинающийся с «/»."
|
||||||
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
|
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
|
||||||
"privateKeyPathDesc" = "Введите полный путь, начинающийся с «/». Перезагрузите панель для применения настроек"
|
"privateKeyPathDesc" = "Введите полный путь, начинающийся с «/»."
|
||||||
"panelUrlPath" = "Корневой путь URL-адреса панели"
|
"panelUrlPath" = "Корневой путь URL-адреса панели"
|
||||||
"panelUrlPathDesc" = "Должен начинаться с «/» и заканчиваться на «/». Перезагрузите панель для применения настроек"
|
"panelUrlPathDesc" = "Должен начинаться с «/» и заканчиваться на «/»."
|
||||||
"oldUsername" = "Имя пользователя сейчас"
|
"oldUsername" = "Текущее имя пользователя"
|
||||||
"currentPassword" = "Пароль сейчас"
|
"currentPassword" = "Текущий пароль"
|
||||||
"newUsername" = "Новое имя пользователя"
|
"newUsername" = "Новое имя пользователя"
|
||||||
"newPassword" = "Новый пароль"
|
"newPassword" = "Новый пароль"
|
||||||
"telegramBotEnable" = "Включить Телеграм-бота"
|
"telegramBotEnable" = "Включить Телеграм-бота"
|
||||||
"telegramBotEnableDesc" = "Перезагрузите панель для применения настроек"
|
"telegramBotEnableDesc" = "Ваш telegram-бот будет взаимодействовать с панелью"
|
||||||
"telegramToken" = "Токен Телеграм-бота"
|
"telegramToken" = "Токен Телеграм-бота"
|
||||||
"telegramTokenDesc" = "Перезагрузите панель для применения настроек"
|
"telegramTokenDesc" = "Токен, который вы получили от @BotFather"
|
||||||
"telegramChatId" = "Телеграм-ID админа бота"
|
"telegramChatId" = "Телеграм-ID админа бота"
|
||||||
"telegramChatIdDesc" = "Множественные идентификаторы чата, разделенные запятыми. Чтобы получить свои идентификаторы чатов, используйте @userinfobot или команду '/id' в боте."
|
"telegramChatIdDesc" = "Несколько идентификаторов чата, разделенных запятой. Используйте @userinfobot или команду '/id' в боте для получения идентификаторов чата."
|
||||||
"telegramNotifyTime" = "Частота уведомлений телеграм-бота"
|
"telegramNotifyTime" = "Частота уведомлений телеграм-бота"
|
||||||
"telegramNotifyTimeDesc" = "Используйте формат Crontab. Перезагрузите панель для применения настроек"
|
"telegramNotifyTimeDesc" = "Используйте временной формат Crontab."
|
||||||
"tgNotifyBackup" = "Резервное копирование базы данных"
|
"tgNotifyBackup" = "Резервное копирование базы данных"
|
||||||
"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете. Перезагрузите панель для применения настроек"
|
"tgNotifyBackupDesc" = "Включение отправки файла резервной копии базы данных с уведомлением об отчете"
|
||||||
"tgNotifyLogin" = "Уведомление о входе"
|
"tgNotifyLogin" = "Уведомление о входе"
|
||||||
"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель."
|
"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель."
|
||||||
"sessionMaxAge" = "Продолжительность сессии"
|
"sessionMaxAge" = "Продолжительность сессии"
|
||||||
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)"
|
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (единица измерения: минута)"
|
||||||
"expireTimeDiff" = "Порог истечения срока сессии для уведомления"
|
"expireTimeDiff" = "Порог истечения срока сессии для уведомления"
|
||||||
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)"
|
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (единица измерения: день)"
|
||||||
"trafficDiff" = "Порог траффика для уведомления"
|
"trafficDiff" = "Порог трафика для уведомления"
|
||||||
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)"
|
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (единица измерения: ГБ)"
|
||||||
"tgNotifyCpu" = "Порог нагрузки на ЦП для уведомления"
|
"tgNotifyCpu" = "Порог нагрузки на ЦП для уведомления"
|
||||||
"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (значение:%)"
|
"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (единица измерения:%)"
|
||||||
"timeZone" = "Временная зона"
|
"timeZone" = "Часовой пояс"
|
||||||
"timeZoneDesc" = "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе. Перезагрузите панель для применения настроек"
|
"timeZoneDesc" = "Запланированные задания выполняются в соответствии со временем в данном часовом поясе."
|
||||||
"subSettings" = "Подписка"
|
"subSettings" = "Подписка"
|
||||||
"subEnable" = "Включить службу"
|
"subEnable" = "Включить службу"
|
||||||
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
||||||
"subListen" = "Прослушивание IP"
|
"subListen" = "Прослушиваемый IP"
|
||||||
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
|
"subListenDesc" = "Оставьте пустым, чтобы прослушивать все IP-адреса"
|
||||||
"subPort" = "Порт подписки"
|
"subPort" = "Порт подписки"
|
||||||
"subPortDesc" = "Номер порта для обслуживания службы подписки не должен использоваться на сервере"
|
"subPortDesc" = "Номер порта для прослушивания службы подписки не должен использоваться на сервере"
|
||||||
"subCertPath" = "Путь к файлу открытого ключа сертификата подписки"
|
"subCertPath" = "Путь к файлу открытого ключа сертификата подписки"
|
||||||
"subCertPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
|
"subCertPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
|
||||||
"subKeyPath" = "Путь к файлу закрытого ключа сертификата подписки"
|
"subKeyPath" = "Путь к файлу закрытого ключа сертификата подписки"
|
||||||
"subKeyPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
|
"subKeyPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
|
||||||
"subPath" = "Корневой путь URL-адреса подписки"
|
"subPath" = "Корневой путь URL-адреса подписки"
|
||||||
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
||||||
"subDomain" = "Домен прослушивания"
|
"subDomain" = "Домен для прослушивания"
|
||||||
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все домены и IP-адреса"
|
"subDomainDesc" = "Оставьте пустым, чтобы прослушивать все домены и IP-адреса"
|
||||||
"subUpdates" = "Интервалы обновления подписки"
|
"subUpdates" = "Интервалы обновления подписки"
|
||||||
"subUpdatesDesc" = "Часовой интервал между обновлениями в клиентском приложении"
|
"subUpdatesDesc" = "Часовой интервал между обновлениями в клиентском приложении"
|
||||||
"subEncrypt" = "Шифровать конфиги"
|
"subEncrypt" = "Шифрование конфигураций"
|
||||||
"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке"
|
"subEncryptDesc" = "Шифрование возвращаемых конфигураций в подписке"
|
||||||
|
"subShowInfo" = "Показать информацию об использовании"
|
||||||
|
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "Шаблоны"
|
"title" = "Шаблоны"
|
||||||
"basicTemplate" = "Базовые шаблоны"
|
"basicTemplate" = "Базовые шаблоны"
|
||||||
"advancedTemplate" = "Расширенные шаблоны"
|
"advancedTemplate" = "Расширенные шаблоны"
|
||||||
"completeTemplate" = "Конфигурация шаблона"
|
"completeTemplate" = "Итоговый шаблон"
|
||||||
"generalConfigs" = "Основные настройки"
|
"generalConfigs" = "Основные настройки"
|
||||||
"generalConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
|
"generalConfigsDesc" = "Общие настройки"
|
||||||
"blockConfigs" = "Блокировка конфигураций"
|
"blockConfigs" = "Блокирующие конфигурации"
|
||||||
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам."
|
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам."
|
||||||
"blockCountryConfigs" = "Заблокировать конфигурации страны"
|
"blockCountryConfigs" = "Конфигурация блокировки стран"
|
||||||
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны."
|
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны."
|
||||||
"directCountryConfigs" = "Прямые настройки страны"
|
"directCountryConfigs" = "Прямые настройки стран"
|
||||||
"directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны."
|
"directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны."
|
||||||
"ipv4Configs" = "Настройки IPv4 "
|
"ipv4Configs" = "Настройки IPv4"
|
||||||
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
||||||
"xrayConfigTemplate" = "Шаблон конфигурации Xray"
|
"xrayConfigTemplate" = "Шаблон конфигурации Xray"
|
||||||
"xrayConfigTemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона. Перезагрузите панель для применения настроек"
|
"xrayConfigTemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона."
|
||||||
"xrayConfigFreedomStrategy" = "Настроить стратегию протокола Freedom"
|
"xrayConfigFreedomStrategy" = "Настроить стратегию протокола Freedom"
|
||||||
"xrayConfigFreedomStrategyDesc" = "Установить стратегию вывода сети в протоколе Freedom"
|
"xrayConfigFreedomStrategyDesc" = "Установить стратегию вывода сети в протоколе Freedom"
|
||||||
"xrayConfigRoutingStrategy" = "Настроить доменную стратегию маршрутизации"
|
"xrayConfigRoutingStrategy" = "Настроить доменную стратегию маршрутизации"
|
||||||
"xrayConfigRoutingStrategyDesc" = "Установить общую стратегию маршрутизации разрешения DNS"
|
"xrayConfigRoutingStrategyDesc" = "Установить общую стратегию маршрутизации разрешения DNS"
|
||||||
"xrayConfigTorrent" = "Запретить использование BitTorrent"
|
"xrayConfigTorrent" = "Запретить использование BitTorrent"
|
||||||
"xrayConfigTorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent. Перезагрузите панель для применения настроек"
|
"xrayConfigTorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent."
|
||||||
"xrayConfigPrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
|
"xrayConfigPrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
|
||||||
"xrayConfigPrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов. Перезагрузите панель для применения настроек"
|
"xrayConfigPrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов."
|
||||||
"xrayConfigAds" = "Бокировка рекламы"
|
"xrayConfigAds" = "Блокировка рекламы"
|
||||||
"xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу. Перезагрузите панель для применения настроек"
|
"xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу."
|
||||||
"xrayConfigFamily" = "Включить семейную конфигурацию"
|
"xrayConfigFamily" = "Включить семейную конфигурацию"
|
||||||
"xrayConfigFamilyDesc" = "Избегайте подключения к небезопасным веб-сайтам для всей семьи"
|
"xrayConfigFamilyDesc" = "Избегать подключения к небезопасным веб-сайтам для всей семьи"
|
||||||
"xrayConfigIRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
"xrayConfigIRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
||||||
"xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана. Перезагрузите панель для применения настроек"
|
"xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана."
|
||||||
"xrayConfigIRDomain" = "Отключить подключение к доменам Ирана"
|
"xrayConfigIRDomain" = "Отключить подключение к доменам Ирана"
|
||||||
"xrayConfigIRDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Ирана. Перезагрузите панель для применения настроек"
|
"xrayConfigIRDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Ирана."
|
||||||
"xrayConfigChinaIp" = "Отключить подключение к диапазонам IP-адресов Китая"
|
"xrayConfigChinaIp" = "Отключить подключение к диапазонам IP-адресов Китая"
|
||||||
"xrayConfigChinaIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Китая. Перезагрузите панель для применения настроек"
|
"xrayConfigChinaIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Китая."
|
||||||
"xrayConfigChinaDomain" = "Отключить подключение к доменам Китая"
|
"xrayConfigChinaDomain" = "Отключить подключение к доменам Китая"
|
||||||
"xrayConfigChinaDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Китая. Перезагрузите панель для применения настроек"
|
"xrayConfigChinaDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Китая."
|
||||||
"xrayConfigRussiaIp" = "Отключить подключение к диапазонам IP-адресов России"
|
"xrayConfigRussiaIp" = "Отключить подключение к диапазонам IP-адресов России"
|
||||||
"xrayConfigRussiaIpDesc" = "Измените конфигурацию, чтобы отключить соединения с диапазонами IP-адресов России. Перезагрузите панель для применения настроек"
|
"xrayConfigRussiaIpDesc" = "Измените конфигурацию, чтобы отключить соединения с диапазонами IP-адресов России."
|
||||||
"xrayConfigRussiaDomain" = "Отключить подключение к доменам России"
|
"xrayConfigRussiaDomain" = "Отключить подключение к доменам России"
|
||||||
"xrayConfigRussiaDomainDesc" = "Измените конфигурацию, чтобы избежать подключения к доменам России. Перезагрузите панель для применения настроек"
|
"xrayConfigRussiaDomainDesc" = "Измените конфигурацию, чтобы избежать подключения к доменам России."
|
||||||
"xrayConfigDirectIRIp" = "Прямое подключение к диапазонам IP-адресов Ирана"
|
"xrayConfigDirectIRIp" = "Прямое подключение к диапазонам IP-адресов Ирана"
|
||||||
"xrayConfigDirectIRIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Ирана"
|
"xrayConfigDirectIRIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Ирана"
|
||||||
"xrayConfigDirectIRDomain" = "Прямое подключение к доменам Ирана"
|
"xrayConfigDirectIRDomain" = "Прямое подключение к доменам Ирана"
|
||||||
@@ -323,16 +324,16 @@
|
|||||||
"xrayConfigDirectRussiaDomain" = "Прямое подключение к доменам России"
|
"xrayConfigDirectRussiaDomain" = "Прямое подключение к доменам России"
|
||||||
"xrayConfigDirectRussiaDomainDesc" = "Изменить шаблон конфигурации для прямого подключения к доменам России"
|
"xrayConfigDirectRussiaDomainDesc" = "Изменить шаблон конфигурации для прямого подключения к доменам России"
|
||||||
"xrayConfigGoogleIPv4" = "Использовать IPv4 для Google"
|
"xrayConfigGoogleIPv4" = "Использовать IPv4 для Google"
|
||||||
"xrayConfigGoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4. Перезагрузите панель для применения настроек"
|
"xrayConfigGoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4."
|
||||||
"xrayConfigNetflixIPv4" = "Использовать IPv4 для Netflix"
|
"xrayConfigNetflixIPv4" = "Использовать IPv4 для Netflix"
|
||||||
"xrayConfigNetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4. Перезагрузите панель для применения настроек"
|
"xrayConfigNetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4."
|
||||||
"xrayConfigInbounds" = "Конфигурация подключений"
|
"xrayConfigInbounds" = "Конфигурация подключений"
|
||||||
"xrayConfigInboundsDesc" = "Изменение шаблона конфигурации, для подключения определенных пользователей. Перезагрузите панель для применения настроек"
|
"xrayConfigInboundsDesc" = "Изменение шаблона конфигурации, для подключения определенных пользователей."
|
||||||
"xrayConfigOutbounds" = "Конфигурация исходящих"
|
"xrayConfigOutbounds" = "Конфигурация исходящих"
|
||||||
"xrayConfigOutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера. Перезагрузите панель для применения настроек"
|
"xrayConfigOutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера."
|
||||||
"xrayConfigRoutings" = "Настройка правил маршрутизации"
|
"xrayConfigRoutings" = "Настройка правил маршрутизации"
|
||||||
"xrayConfigRoutingsDesc" = "Изменение шаблона конфигурации, для определения правил маршрутизации для этого сервера. Перезагрузите панель для применения настроек"
|
"xrayConfigRoutingsDesc" = "Изменение шаблона конфигурации, для определения правил маршрутизации для этого сервера."
|
||||||
"manualLists" = "ручные списки"
|
"manualLists" = "Пользовательские списки"
|
||||||
"manualListsDesc" = "Пожалуйста, используйте формат массива JSON"
|
"manualListsDesc" = "Пожалуйста, используйте формат массива JSON"
|
||||||
"manualBlockedIPs" = "Список заблокированных IP-адресов"
|
"manualBlockedIPs" = "Список заблокированных IP-адресов"
|
||||||
"manualBlockedDomains" = "Список заблокированных доменов"
|
"manualBlockedDomains" = "Список заблокированных доменов"
|
||||||
@@ -350,12 +351,12 @@
|
|||||||
[tgbot]
|
[tgbot]
|
||||||
"noResult" = "❗ Нет результатов!"
|
"noResult" = "❗ Нет результатов!"
|
||||||
"wentWrong" = "❌ Что-то пошло не так!"
|
"wentWrong" = "❌ Что-то пошло не так!"
|
||||||
"noInbounds" = "❗ Входящих соединений не найдено!"
|
"noInbounds" = "❗ Подключений не найдено!"
|
||||||
"unlimited" = "♾ Неограниченно"
|
"unlimited" = "♾ Неограниченно"
|
||||||
"day" = "День"
|
"day" = "День"
|
||||||
"days" = "Дней"
|
"days" = "Дней"
|
||||||
"unknown" = "Неизвестно"
|
"unknown" = "Неизвестно"
|
||||||
"inbounds" = "Входящие"
|
"inbounds" = "Подключения"
|
||||||
"clients" = "Клиенты"
|
"clients" = "Клиенты"
|
||||||
|
|
||||||
[tgbot.commands]
|
[tgbot.commands]
|
||||||
@@ -399,21 +400,21 @@
|
|||||||
"upload" = "🔼 Загрузка↑: {{ .Upload }}\r\n"
|
"upload" = "🔼 Загрузка↑: {{ .Upload }}\r\n"
|
||||||
"download" = "🔽 Скачивание↓: {{ .Download }}\r\n"
|
"download" = "🔽 Скачивание↓: {{ .Download }}\r\n"
|
||||||
"total" = "🔄 Всего: {{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "🔄 Всего: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n"
|
"exhaustedMsg" = "🚨 Истекли {{ .Type }}:\r\n"
|
||||||
"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n"
|
"exhaustedCount" = "🚨 Количество истекших {{ .Type }}:\r\n"
|
||||||
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 Скоро отключатся: {{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"dbBackup" = "Получить резервную копию DB"
|
"dbBackup" = "Получить резервную копию базы данных"
|
||||||
"serverUsage" = "Использование сервера"
|
"serverUsage" = "Использование сервера"
|
||||||
"getInbounds" = "Получить входящие потоки"
|
"getInbounds" = "Получить список подключений"
|
||||||
"depleteSoon" = "Скоро исчерпание"
|
"depleteSoon" = "Скоро отключатся"
|
||||||
"clientUsage" = "Получить использование"
|
"clientUsage" = "Получить статистику"
|
||||||
"commands" = "Команды"
|
"commands" = "Команды"
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"getInboundsFailed" = "❌ Не удалось получить входящие потоки."
|
"getInboundsFailed" = "❌ Не удалось получить подключения."
|
||||||
"askToAddUser" = "Конфигурация не найдена!\r\nВы должны настроить свое телеграм-имя пользователя и попросить вашего администратора добавить его в вашу конфигурацию."
|
"askToAddUser" = "Конфигурация не найдена!\r\nВы должны настроить свое имя пользователя Telegram и попросить вашего администратора добавить его в вашу конфигурацию."
|
||||||
"askToAddUserName" = "Конфигурация не найдена!\r\nПожалуйста, попросите вашего администратора использовать ваше телеграм-имя пользователя в вашей конфигурации(ях).\r\n\r\nВаше имя пользователя: <b>@{{ .TgUserName }}</b>"
|
"askToAddUserName" = "Конфигурация не найдена!\r\nПожалуйста, попросите вашего администратора использовать ваше имя пользователя Telegram в вашей конфигурации(ях).\r\n\r\nВаше имя пользователя: <b>@{{ .TgUserName }}</b>"
|
||||||
|
|||||||
@@ -127,7 +127,6 @@
|
|||||||
"network" = "网络"
|
"network" = "网络"
|
||||||
"destinationPort" = "目标端口"
|
"destinationPort" = "目标端口"
|
||||||
"targetAddress" = "目标地址"
|
"targetAddress" = "目标地址"
|
||||||
"disableInsecureEncryption" = "禁用不安全加密"
|
|
||||||
"monitorDesc" = "默认留空即可"
|
"monitorDesc" = "默认留空即可"
|
||||||
"meansNoLimit" = "表示不限制"
|
"meansNoLimit" = "表示不限制"
|
||||||
"totalFlow" = "总流量"
|
"totalFlow" = "总流量"
|
||||||
@@ -268,6 +267,8 @@
|
|||||||
"subUpdatesDesc" = "客户端应用程序更新之间的间隔时间"
|
"subUpdatesDesc" = "客户端应用程序更新之间的间隔时间"
|
||||||
"subEncrypt" = "加密配置"
|
"subEncrypt" = "加密配置"
|
||||||
"subEncryptDesc" = "在订阅中加密返回的配置"
|
"subEncryptDesc" = "在订阅中加密返回的配置"
|
||||||
|
"subShowInfo" = "显示使用信息"
|
||||||
|
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "模板"
|
"title" = "模板"
|
||||||
|
|||||||
@@ -244,9 +244,6 @@ func (s *Server) startTask() {
|
|||||||
s.cron.AddJob("@every 10s", job.NewXrayTrafficJob())
|
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
|
// Make a traffic condition every day, 8:30
|
||||||
var entry cron.EntryID
|
var entry cron.EntryID
|
||||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||||
|
|||||||
7
x-ui.sh
7
x-ui.sh
@@ -623,7 +623,6 @@ show_usage() {
|
|||||||
echo "x-ui enable - Enable x-ui on system startup"
|
echo "x-ui enable - Enable x-ui on system startup"
|
||||||
echo "x-ui disable - Disable 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 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 update - Update x-ui"
|
||||||
echo "x-ui install - Install x-ui"
|
echo "x-ui install - Install x-ui"
|
||||||
echo "x-ui uninstall - Uninstall x-ui"
|
echo "x-ui uninstall - Uninstall x-ui"
|
||||||
@@ -645,12 +644,12 @@ show_menu() {
|
|||||||
${green}7.${plain} View current panel settings
|
${green}7.${plain} View current panel settings
|
||||||
————————————————
|
————————————————
|
||||||
${green}8.${plain} Start x-ui
|
${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}10.${plain} Reboot x-ui
|
||||||
${green}11.${plain} Check x-ui state
|
${green}11.${plain} Check x-ui state
|
||||||
${green}12.${plain} Check x-ui logs
|
${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}14.${plain} Cancel x-ui Autostart
|
||||||
————————————————
|
————————————————
|
||||||
${green}15.${plain} 一A key installation bbr (latest kernel)
|
${green}15.${plain} 一A key installation bbr (latest kernel)
|
||||||
@@ -658,7 +657,7 @@ show_menu() {
|
|||||||
${green}17.${plain} 一Cloudflare SSL Certificate
|
${green}17.${plain} 一Cloudflare SSL Certificate
|
||||||
"
|
"
|
||||||
show_status
|
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
|
case "${num}" in
|
||||||
0)
|
0)
|
||||||
|
|||||||
Reference in New Issue
Block a user