mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-19 15:25:49 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9adc2054a0 | ||
|
|
5efedd3a5e | ||
|
|
b4e2bba934 | ||
|
|
40e4145263 | ||
|
|
2b8c913be9 | ||
|
|
3d1fd65c3a | ||
|
|
5b306e57c9 | ||
|
|
102f8bab51 | ||
|
|
e4a011b6d9 | ||
|
|
7f20b71c45 | ||
|
|
4779b37e6e | ||
|
|
7a20d2c83c | ||
|
|
7ead999f12 | ||
|
|
812c9fbe17 | ||
|
|
5adbfd9528 | ||
|
|
1b93c7c0f2 | ||
|
|
58e7f51313 | ||
|
|
78e97af4db |
@@ -92,6 +92,7 @@ docker build -t x-ui .
|
|||||||
- Search within all inbounds and clients
|
- Search within all inbounds and clients
|
||||||
- Support Dark/Light theme UI
|
- Support Dark/Light theme UI
|
||||||
- Support multi-user multi-protocol, web page visualization operation
|
- Support multi-user multi-protocol, web page visualization operation
|
||||||
|
- Support multi-domain configuration and multi-certificate inbounds
|
||||||
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
||||||
- Support for configuring more transport configurations
|
- Support for configuring more transport configurations
|
||||||
- Traffic statistics, limit traffic, limit expiration time
|
- Traffic statistics, limit traffic, limit expiration time
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.3.0
|
1.4.0
|
||||||
4
go.mod
4
go.mod
@@ -3,7 +3,7 @@ module x-ui
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Workiva/go-datastructures v1.0.53
|
github.com/Workiva/go-datastructures v1.1.0
|
||||||
github.com/gin-contrib/sessions v0.0.4
|
github.com/gin-contrib/sessions v0.0.4
|
||||||
github.com/gin-gonic/gin v1.9.0
|
github.com/gin-gonic/gin v1.9.0
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
@@ -17,7 +17,7 @@ require (
|
|||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.9.0
|
||||||
google.golang.org/grpc v1.55.0
|
google.golang.org/grpc v1.55.0
|
||||||
gorm.io/driver/sqlite v1.5.0
|
gorm.io/driver/sqlite v1.5.1
|
||||||
gorm.io/gorm v1.25.1
|
gorm.io/gorm v1.25.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -1,8 +1,8 @@
|
|||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
|
||||||
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
github.com/Workiva/go-datastructures v1.1.0/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||||
@@ -91,7 +91,6 @@ github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQ
|
|||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
@@ -246,9 +245,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
gorm.io/driver/sqlite v1.5.1 h1:hYyrLkAWE71bcarJDPdZNTLWtr8XrSjOWyjUYI6xdL4=
|
||||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
gorm.io/driver/sqlite v1.5.1/go.mod h1:7MZZ2Z8bqyfSQA1gYEV6MagQWj3cpUkJj9Z+d1HEMEQ=
|
||||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
|
||||||
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
|
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
|
||||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||||
|
|||||||
26
main.go
26
main.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/sub"
|
||||||
"x-ui/v2ui"
|
"x-ui/v2ui"
|
||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
@@ -50,6 +51,16 @@ func runWebServer() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subServer *sub.Server
|
||||||
|
subServer = sub.NewServer()
|
||||||
|
global.SetSubServer(subServer)
|
||||||
|
|
||||||
|
err = subServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
// Trap shutdown signals
|
// Trap shutdown signals
|
||||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
||||||
@@ -62,6 +73,11 @@ func runWebServer() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("stop server err:", err)
|
logger.Warning("stop server err:", err)
|
||||||
}
|
}
|
||||||
|
err = subServer.Stop()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("stop server err:", err)
|
||||||
|
}
|
||||||
|
|
||||||
server = web.NewServer()
|
server = web.NewServer()
|
||||||
global.SetWebServer(server)
|
global.SetWebServer(server)
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
@@ -69,8 +85,18 @@ func runWebServer() {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subServer = sub.NewServer()
|
||||||
|
global.SetSubServer(subServer)
|
||||||
|
|
||||||
|
err = subServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
server.Stop()
|
server.Stop()
|
||||||
|
subServer.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
171
sub/sub.go
Normal file
171
sub/sub.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"x-ui/config"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/network"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
httpServer *http.Server
|
||||||
|
listener net.Listener
|
||||||
|
|
||||||
|
sub *SUBController
|
||||||
|
settingService service.SettingService
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer() *Server {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
return &Server{
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
|
if config.IsDebug() {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
} else {
|
||||||
|
gin.DefaultWriter = io.Discard
|
||||||
|
gin.DefaultErrorWriter = io.Discard
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := gin.Default()
|
||||||
|
|
||||||
|
subPath, err := s.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subDomain, err := s.settingService.GetSubDomain()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if subDomain != "" {
|
||||||
|
validateDomain := func(c *gin.Context) {
|
||||||
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
|
|
||||||
|
if host != subDomain {
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.Use(validateDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := engine.Group(subPath)
|
||||||
|
|
||||||
|
s.sub = NewSUBController(g)
|
||||||
|
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() (err error) {
|
||||||
|
//This is an anonymous function, no function name
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
s.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
subEnable, err := s.settingService.GetSubEnable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !subEnable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
engine, err := s.initRouter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile, err := s.settingService.GetSubCertFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyFile, err := s.settingService.GetSubKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listen, err := s.settingService.GetSubListen()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
port, err := s.settingService.GetSubPort()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
|
||||||
|
listener, err := net.Listen("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if certFile != "" || keyFile != "" {
|
||||||
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
listener.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}
|
||||||
|
listener = network.NewAutoHttpsListener(listener)
|
||||||
|
listener = tls.NewListener(listener, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if certFile != "" || keyFile != "" {
|
||||||
|
logger.Info("Sub server run https on", listener.Addr())
|
||||||
|
} else {
|
||||||
|
logger.Info("Sub server run http on", listener.Addr())
|
||||||
|
}
|
||||||
|
s.listener = listener
|
||||||
|
|
||||||
|
s.httpServer = &http.Server{
|
||||||
|
Handler: engine,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.httpServer.Serve(listener)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() error {
|
||||||
|
s.cancel()
|
||||||
|
|
||||||
|
var err1 error
|
||||||
|
var err2 error
|
||||||
|
if s.httpServer != nil {
|
||||||
|
err1 = s.httpServer.Shutdown(s.ctx)
|
||||||
|
}
|
||||||
|
if s.listener != nil {
|
||||||
|
err2 = s.listener.Close()
|
||||||
|
}
|
||||||
|
return common.Combine(err1, err2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetCtx() context.Context {
|
||||||
|
return s.ctx
|
||||||
|
}
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
package controller
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"strings"
|
"strings"
|
||||||
"x-ui/web/service"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SUBController struct {
|
type SUBController struct {
|
||||||
BaseController
|
subService SubService
|
||||||
|
|
||||||
subService service.SubService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
||||||
@@ -21,7 +18,7 @@ func NewSUBController(g *gin.RouterGroup) *SUBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/sub")
|
g = g.Group("/")
|
||||||
|
|
||||||
g.GET("/:subid", a.subs)
|
g.GET("/:subid", a.subs)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/web/service"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
@@ -15,7 +16,8 @@ import (
|
|||||||
|
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
address string
|
address string
|
||||||
inboundService InboundService
|
inboundService service.InboundService
|
||||||
|
settingServics service.SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string) ([]string, []string, error) {
|
func (s *SubService) GetSubs(subId string, host string) ([]string, []string, error) {
|
||||||
@@ -29,7 +31,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, []string, err
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.getClients(inbound)
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
||||||
}
|
}
|
||||||
@@ -66,7 +68,8 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, []string, err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
||||||
headers = append(headers, "12")
|
updateInterval, _ := s.settingServics.GetSubUpdates()
|
||||||
|
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||||
headers = append(headers, subId)
|
headers = append(headers, subId)
|
||||||
return result, headers, nil
|
return result, headers, nil
|
||||||
}
|
}
|
||||||
@@ -163,6 +166,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -185,6 +189,9 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
obj["allowInsecure"], _ = insecure.(bool)
|
obj["allowInsecure"], _ = insecure.(bool)
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
if serverName != "" {
|
if serverName != "" {
|
||||||
@@ -192,7 +199,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, _ := s.inboundService.getClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -203,6 +210,21 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
obj["id"] = clients[clientIndex].ID
|
obj["id"] = clients[clientIndex].ID
|
||||||
obj["aid"] = clients[clientIndex].AlterIds
|
obj["aid"] = clients[clientIndex].AlterIds
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
obj["ps"] = remark + "-" + domain["remark"].(string)
|
||||||
|
obj["add"] = domain["domain"].(string)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
|
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
}
|
}
|
||||||
@@ -214,7 +236,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]interface{}
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.getClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -270,6 +292,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -294,6 +317,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -354,8 +380,22 @@ 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)
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
url.Fragment = remark + "-" + domain["remark"].(string)
|
||||||
|
url.Host = domain["domain"].(string)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
url.Fragment = remark
|
url.Fragment = remark
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
@@ -367,7 +407,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]interface{}
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.getClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -423,6 +463,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -447,6 +488,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
@@ -506,6 +550,21 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
url.Fragment = remark + "-" + domain["remark"].(string)
|
||||||
|
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
url.Fragment = remark
|
url.Fragment = remark
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
@@ -517,7 +576,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]interface{}
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.getClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]interface{}
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
@@ -9,7 +9,6 @@ body {
|
|||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 100vh;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -151,9 +151,9 @@ class DBInbound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(clientIndex) {
|
genLink(address=this.address, remark=this.remark, clientIndex=0) {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genLink(this.address, this.remark, clientIndex);
|
return inbound.genLink(address, remark, clientIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
get genInboundLinks() {
|
get genInboundLinks() {
|
||||||
@@ -180,6 +180,14 @@ class AllSetting {
|
|||||||
this.tgBotBackup = false;
|
this.tgBotBackup = false;
|
||||||
this.tgCpu = "";
|
this.tgCpu = "";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
|
this.subEnable = false;
|
||||||
|
this.subListen = "";
|
||||||
|
this.subPort = "2096";
|
||||||
|
this.subPath = "sub/";
|
||||||
|
this.subDomain = "";
|
||||||
|
this.subCertFile = "";
|
||||||
|
this.subKeyFile = "";
|
||||||
|
this.subUpdates = 0;
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|||||||
@@ -494,7 +494,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName, json.settings.domains);
|
||||||
}
|
}
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
@@ -562,17 +562,19 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(allowInsecure = false, fingerprint = '', serverName = '') {
|
constructor(allowInsecure = false, fingerprint = '', serverName = '', domains = []) {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
|
this.domains = domains;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new TlsStreamSettings.Settings(
|
return new TlsStreamSettings.Settings(
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.servername,
|
json.serverName,
|
||||||
|
json.domains,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -580,6 +582,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
|||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
serverName: this.serverName,
|
||||||
|
domains: this.domains,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1380,24 +1383,12 @@ class Inbound extends XrayCommonClass {
|
|||||||
genLink(address='', remark='', clientIndex=0) {
|
genLink(address='', remark='', clientIndex=0) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
if (this.settings.vmesses[clientIndex].email != ""){
|
|
||||||
remark = this.settings.vmesses[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genVmessLink(address, remark, clientIndex);
|
return this.genVmessLink(address, remark, clientIndex);
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
if (this.settings.vlesses[clientIndex].email != ""){
|
|
||||||
remark = this.settings.vlesses[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genVLESSLink(address, remark, clientIndex);
|
return this.genVLESSLink(address, remark, clientIndex);
|
||||||
case Protocols.SHADOWSOCKS:
|
case Protocols.SHADOWSOCKS:
|
||||||
if (this.settings.shadowsockses[clientIndex].email != ""){
|
|
||||||
remark = this.settings.shadowsockses[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genSSLink(address, remark, clientIndex);
|
return this.genSSLink(address, remark, clientIndex);
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if (this.settings.trojans[clientIndex].email != ""){
|
|
||||||
remark = this.settings.trojans[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genTrojanLink(address, remark, clientIndex);
|
return this.genTrojanLink(address, remark, clientIndex);
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
@@ -1409,12 +1400,17 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
JSON.parse(this.settings).clients.forEach((_,index) => {
|
case Protocols.SHADOWSOCKS:
|
||||||
link += this.genLink(address, remark, index) + '\r\n';
|
JSON.parse(this.settings).clients.forEach((client,index) => {
|
||||||
|
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
||||||
|
this.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
link += this.genLink(domain.domain, remark + '-' + client.email + '-' + domain.remark, index) + '\r\n';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
link += this.genLink(address, remark + '-' + client.email, index) + '\r\n';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return link;
|
return link;
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return (this.genSSLink(address, remark) + '\r\n');
|
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,11 +70,56 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
|||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
tgBotEnable, err := a.settingService.GetTgbotenabled()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subEnable, err := a.settingService.GetSubEnable()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subPort, err := a.settingService.GetSubPort()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subPath, err := a.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subDomain, err := a.settingService.GetSubDomain()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subKeyFile, err := a.settingService.GetSubKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subCertFile, err := a.settingService.GetSubCertFile()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subTLS := false
|
||||||
|
if subKeyFile != "" || subCertFile != "" {
|
||||||
|
subTLS = true
|
||||||
|
}
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"expireDiff": expireDiff,
|
"expireDiff": expireDiff,
|
||||||
"trafficDiff": trafficDiff,
|
"trafficDiff": trafficDiff,
|
||||||
"defaultCert": defaultCert,
|
"defaultCert": defaultCert,
|
||||||
"defaultKey": defaultKey,
|
"defaultKey": defaultKey,
|
||||||
|
"tgBotEnable": tgBotEnable,
|
||||||
|
"subEnable": subEnable,
|
||||||
|
"subPort": subPort,
|
||||||
|
"subPath": subPath,
|
||||||
|
"subDomain": subDomain,
|
||||||
|
"subTLS": subTLS,
|
||||||
}
|
}
|
||||||
jsonObj(c, result, nil)
|
jsonObj(c, result, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ type AllSetting struct {
|
|||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
|
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||||
|
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||||
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
|
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
@@ -53,10 +61,25 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubListen != "" {
|
||||||
|
ip := net.ParseIP(s.SubListen)
|
||||||
|
if ip == nil {
|
||||||
|
return common.NewError("Sub listen is not valid ip:", s.SubListen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if s.WebPort <= 0 || s.WebPort > 65535 {
|
if s.WebPort <= 0 || s.WebPort > 65535 {
|
||||||
return common.NewError("web port is not a valid port:", s.WebPort)
|
return common.NewError("web port is not a valid port:", s.WebPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubPort <= 0 || s.SubPort > 65535 {
|
||||||
|
return common.NewError("Sub port is not a valid port:", s.SubPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.SubPort == s.WebPort {
|
||||||
|
return common.NewError("Sub and Web could not use same port:", s.SubPort)
|
||||||
|
}
|
||||||
|
|
||||||
if s.WebCertFile != "" || s.WebKeyFile != "" {
|
if s.WebCertFile != "" || s.WebKeyFile != "" {
|
||||||
_, err := tls.LoadX509KeyPair(s.WebCertFile, s.WebKeyFile)
|
_, err := tls.LoadX509KeyPair(s.WebCertFile, s.WebKeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -64,6 +87,13 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubCertFile != "" || s.SubKeyFile != "" {
|
||||||
|
_, err := tls.LoadX509KeyPair(s.SubCertFile, s.SubKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("cert file <%v> or key file <%v> invalid: %v", s.SubCertFile, s.SubKeyFile, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(s.WebBasePath, "/") {
|
if !strings.HasPrefix(s.WebBasePath, "/") {
|
||||||
s.WebBasePath = "/" + s.WebBasePath
|
s.WebBasePath = "/" + s.WebBasePath
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,23 @@ package global
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var webServer WebServer
|
var webServer WebServer
|
||||||
|
var subServer SubServer
|
||||||
|
|
||||||
type WebServer interface {
|
type WebServer interface {
|
||||||
GetCron() *cron.Cron
|
GetCron() *cron.Cron
|
||||||
GetCtx() context.Context
|
GetCtx() context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SubServer interface {
|
||||||
|
GetCtx() context.Context
|
||||||
|
}
|
||||||
|
|
||||||
func SetWebServer(s WebServer) {
|
func SetWebServer(s WebServer) {
|
||||||
webServer = s
|
webServer = s
|
||||||
}
|
}
|
||||||
@@ -20,3 +26,11 @@ func SetWebServer(s WebServer) {
|
|||||||
func GetWebServer() WebServer {
|
func GetWebServer() WebServer {
|
||||||
return webServer
|
return webServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetSubServer(s SubServer) {
|
||||||
|
subServer = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubServer() SubServer {
|
||||||
|
return subServer
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,48 +4,54 @@
|
|||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="300px">
|
width="300px">
|
||||||
<a-tag v-if="qrModal.clientName" color="orange" style="margin-bottom: 10px;display: block;text-align: center;">
|
|
||||||
{{ i18n "pages.inbounds.email" }}: "[[ qrModal.clientName ]]"
|
|
||||||
</a-tag>
|
|
||||||
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
||||||
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
|
<a-divider>Subscription</a-divider>
|
||||||
|
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas>
|
||||||
|
</template>
|
||||||
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
|
<a-tag color="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
||||||
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const qrModal = {
|
const qrModal = {
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
clientIndex: 0,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
copyText: '',
|
client: null,
|
||||||
clientName: null,
|
qrcodes: [],
|
||||||
qrcode: null,
|
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '', clientName = null) {
|
subId: '',
|
||||||
|
show: function (title = '', dbInbound = new DBInbound(), clientIndex = 0) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.content = content;
|
this.clientIndex = clientIndex;
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clientName = clientName;
|
settings = JSON.parse(this.inbound.settings);
|
||||||
if (ObjectUtil.isEmpty(copyText)) {
|
this.client = settings.clients[clientIndex];
|
||||||
this.copyText = content;
|
remark = this.dbInbound.remark + "-" + this.client.email;
|
||||||
|
address = this.dbInbound.address;
|
||||||
|
this.qrcodes = [];
|
||||||
|
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||||
|
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
this.qrcodes.push({
|
||||||
|
remark: remark + "-" + domain.remark,
|
||||||
|
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, clientIndex)
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.copyText = copyText;
|
this.qrcodes.push({
|
||||||
|
remark: remark,
|
||||||
|
link: this.inbound.genLink(address, remark, clientIndex)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
qrModalApp.$nextTick(() => {
|
|
||||||
if (this.qrcode === null) {
|
|
||||||
this.qrcode = new QRious({
|
|
||||||
element: document.querySelector('#qrCode'),
|
|
||||||
size: 260,
|
|
||||||
value: content,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.qrcode.value = content;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
@@ -59,16 +65,40 @@
|
|||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard() {
|
copyToClipboard(elmentId,content) {
|
||||||
this.qrModal.clipboard = new ClipboardJS('#qrCode', {
|
this.qrModal.clipboard = new ClipboardJS('#'+elmentId, {
|
||||||
text: () => this.qrModal.copyText,
|
text: () => content,
|
||||||
});
|
});
|
||||||
this.qrModal.clipboard.on('success', () => {
|
this.qrModal.clipboard.on('success', () => {
|
||||||
app.$message.success('{{ i18n "copied" }}')
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
this.qrModal.clipboard.destroy();
|
this.qrModal.clipboard.destroy();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
setQrCode(elmentId,content) {
|
||||||
|
new QRious({
|
||||||
|
element: document.querySelector('#'+elmentId),
|
||||||
|
size: 260,
|
||||||
|
value: content,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
genSubLink(subID) {
|
||||||
|
protocol = app.subSettings.tls ? "https://" : "http://";
|
||||||
|
hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain;
|
||||||
|
subPort = app.subSettings.port;
|
||||||
|
port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort);
|
||||||
|
subPath = app.subSettings.path;
|
||||||
|
return protocol + hostName + port + subPath + subID;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updated() {
|
||||||
|
if (qrModal.client.subId){
|
||||||
|
qrModal.subId = qrModal.client.subId;
|
||||||
|
this.setQrCode("qrCode-sub",this.genSubLink(this.subId));
|
||||||
|
}
|
||||||
|
qrModal.qrcodes.forEach((element,index) => {
|
||||||
|
this.setQrCode("qrCode-"+index, element.link);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="app.subSettings.enable">
|
||||||
<td>Subscription</td>
|
<td>Subscription</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="app.tgBotEnable">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram Username</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -215,6 +215,7 @@
|
|||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -223,6 +224,7 @@
|
|||||||
case Protocols.VMESS: return new Inbound.VmessSettings.Vmess();
|
case Protocols.VMESS: return new Inbound.VmessSettings.Vmess();
|
||||||
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
||||||
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
||||||
|
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks();
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,16 +11,6 @@
|
|||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>{{ i18n "menu.settings"}}</span>
|
<span>{{ i18n "menu.settings"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-sub-menu>
|
|
||||||
<template slot="title">
|
|
||||||
<a-icon type="link"></a-icon>
|
|
||||||
<span>{{ i18n "menu.link"}}</span>
|
|
||||||
</template>
|
|
||||||
<a-menu-item key="https://github.com/alireza0/x-ui/">
|
|
||||||
<a-icon type="github"></a-icon>
|
|
||||||
<span>Github</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-sub-menu>
|
|
||||||
<a-menu-item key="{{ .base_path }}logout">
|
<a-menu-item key="{{ .base_path }}logout">
|
||||||
<a-icon type="logout"></a-icon>
|
<a-icon type="logout"></a-icon>
|
||||||
<span>{{ i18n "menu.logout"}}</span>
|
<span>{{ i18n "menu.logout"}}</span>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email">
|
<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.randomText(16,16)" type="sync"></a-icon></td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.email">
|
<tr v-if="client.email && app.tgBotEnable">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram Username</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
|
|||||||
@@ -29,18 +29,18 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="client.email && app.subSettings.enable">
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item v-if="client.email">
|
<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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="client.email && app.tgBotEnable">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram Username</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -27,18 +27,18 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="client.email && app.subSettings.enable">
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item v-if="client.email">
|
<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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="client.email && app.tgBotEnable">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram Username</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -38,18 +38,18 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="client.email && app.subSettings.enable">
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item v-if="client.email">
|
<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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="client.email && app.tgBotEnable">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram Username</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -35,18 +35,18 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="client.email && app.subSettings.enable">
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomText(16,16)" type="sync"></a-icon></td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item v-if="client.email">
|
<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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="client.email && app.tgBotEnable">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram Username</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -65,6 +65,27 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>Multi Domain</td>
|
||||||
|
<td>
|
||||||
|
<a-switch v-model="multiDomain"></a-switch>
|
||||||
|
<a-button v-if="multiDomain" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})">+</a-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="multiDomain">
|
||||||
|
<td colspan="2">
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
|
||||||
|
<a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
|
||||||
|
<a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
|
||||||
|
<template slot="addonAfter">
|
||||||
|
<a-button size="small" style="margin: 0px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else>
|
||||||
<td>{{ i18n "domainName" }}</td>
|
<td>{{ i18n "domainName" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -97,9 +118,9 @@
|
|||||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
<a-button type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin: 0 10px">+</a-button>
|
|
||||||
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)">-</a-button>
|
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
||||||
|
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-tag :color="statsColor(record, client.email)" @click="alert(usageColor(0,1024,512))">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
|
<a-tag :color="statsColor(record, client.email)">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
|
||||||
<template v-if="client._totalGB > 0">
|
<template v-if="client._totalGB > 0">
|
||||||
<a-tag :color="statsColor(record, client.email)">[[client._totalGB]]GB</a-tag>
|
<a-tag :color="statsColor(record, client.email)">[[client._totalGB]]GB</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -102,19 +102,16 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table v-if="infoModal.clientSettings.subId + infoModal.clientSettings.tgId" style="margin-bottom: 10px;">
|
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||||
<tr v-if="infoModal.clientSettings.subId">
|
<a-divider>Subscription link</a-divider>
|
||||||
<td>Subscription link</td>
|
<a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
|
||||||
<td>
|
<a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"></a-icon>
|
||||||
<a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a>
|
</template>
|
||||||
<a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', subBase + infoModal.clientSettings.subId)"></a-icon>
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
</td>
|
<a-divider>Telegram Username</a-divider>
|
||||||
</tr>
|
<a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a>
|
||||||
<tr v-if="infoModal.clientSettings.tgId">
|
<a-icon id="copy-tg-link" type="snippets" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)"></a-icon>
|
||||||
<td>Telegram Username</td>
|
</template>
|
||||||
<td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-divider></a-divider>
|
<a-divider></a-divider>
|
||||||
@@ -178,10 +175,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<div v-if="dbInbound.hasLink()">
|
<div v-if="dbInbound.hasLink()">
|
||||||
<a-divider>URL</a-divider>
|
<a-divider>URL</a-divider>
|
||||||
<p>[[ infoModal.link ]]</p>
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-url-link" @click="copyToClipboard('copy-url-link', infoModal.link)">
|
<a-col :span="21"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
<a-icon type="snippets"></a-icon>{{ i18n "copy" }}
|
<a-col :span="3" style="text-align: right;">
|
||||||
</button>
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
|
<a-icon type="snippets"></a-icon>{{ i18n "copy" }}
|
||||||
|
</button>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
@@ -195,23 +196,56 @@
|
|||||||
upStats: 0,
|
upStats: 0,
|
||||||
downStats: 0,
|
downStats: 0,
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
link: null,
|
links: [],
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
|
subLink: '',
|
||||||
|
tgLink: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.link = dbInbound.genLink(index);
|
|
||||||
this.settings = JSON.parse(this.inbound.settings);
|
this.settings = JSON.parse(this.inbound.settings);
|
||||||
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.email;
|
||||||
|
address = this.dbInbound.address;
|
||||||
|
this.links = [];
|
||||||
|
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||||
|
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
this.links.push({
|
||||||
|
remark: remark + "-" + domain.remark,
|
||||||
|
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, index)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.links.push({
|
||||||
|
remark: remark,
|
||||||
|
link: this.inbound.genLink(address, remark, index)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.clientSettings) {
|
||||||
|
if (this.clientSettings.subId) {
|
||||||
|
this.subLink = this.genSubLink(this.clientSettings.subId);
|
||||||
|
}
|
||||||
|
if (this.clientSettings.tgId) {
|
||||||
|
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
},
|
},
|
||||||
|
genSubLink(subID) {
|
||||||
|
protocol = app.subSettings.tls ? "https://" : "http://";
|
||||||
|
hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain;
|
||||||
|
subPort = app.subSettings.port;
|
||||||
|
port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort);
|
||||||
|
subPath = app.subSettings.path;
|
||||||
|
return protocol + hostName + port + subPath + subID;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const infoModalApp = new Vue({
|
const infoModalApp = new Vue({
|
||||||
@@ -237,12 +271,6 @@
|
|||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return infoModal.dbInbound.isEnable;
|
||||||
},
|
},
|
||||||
get subBase() {
|
|
||||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + basePath + "sub/";
|
|
||||||
},
|
|
||||||
get tgBase() {
|
|
||||||
return "https://t.me/"
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard(elmentId,content) {
|
copyToClipboard(elmentId,content) {
|
||||||
|
|||||||
@@ -90,6 +90,18 @@
|
|||||||
set delayedExpireDays(days){
|
set delayedExpireDays(days){
|
||||||
this.client.expiryTime = -86400000 * days;
|
this.client.expiryTime = -86400000 * days;
|
||||||
},
|
},
|
||||||
|
get multiDomain() {
|
||||||
|
return this.inbound.stream.tls.settings.domains.length > 0;
|
||||||
|
},
|
||||||
|
set multiDomain(value) {
|
||||||
|
if (value) {
|
||||||
|
inModal.inbound.stream.tls.server = "";
|
||||||
|
inModal.inbound.stream.tls.settings.domains = [{remark: "", domain: window.location.host.split(":")[0]}];
|
||||||
|
} else {
|
||||||
|
inModal.inbound.stream.tls.server = "";
|
||||||
|
inModal.inbound.stream.tls.settings.domains = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange() {
|
streamNetworkChange() {
|
||||||
|
|||||||
@@ -104,7 +104,17 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
<a-switch v-model="enableFilter"
|
||||||
|
checked-children="{{ i18n "search" }}" un-checked-children="{{ i18n "filter" }}"
|
||||||
|
@change="toggleFilter">
|
||||||
|
</a-switch>
|
||||||
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
||||||
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
||||||
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
|
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||||
|
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||||
|
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1300 }"
|
:loading="spinning" :scroll="{ x: 1300 }"
|
||||||
@@ -319,6 +329,8 @@
|
|||||||
inbounds: [],
|
inbounds: [],
|
||||||
dbInbounds: [],
|
dbInbounds: [],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
|
enableFilter: false,
|
||||||
|
filterBy: '',
|
||||||
searchedInbounds: [],
|
searchedInbounds: [],
|
||||||
expireDiff: 0,
|
expireDiff: 0,
|
||||||
trafficDiff: 0,
|
trafficDiff: 0,
|
||||||
@@ -328,6 +340,14 @@
|
|||||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
|
subSettings: {
|
||||||
|
enable : false,
|
||||||
|
port: 0,
|
||||||
|
path: '',
|
||||||
|
domain: '',
|
||||||
|
tls: false
|
||||||
|
},
|
||||||
|
tgBotEnable: false
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -349,10 +369,20 @@
|
|||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.expireDiff = msg.obj.expireDiff * 86400000;
|
with(msg.obj){
|
||||||
this.trafficDiff = msg.obj.trafficDiff * 1073741824;
|
this.expireDiff = expireDiff * 86400000;
|
||||||
this.defaultCert = msg.obj.defaultCert;
|
this.trafficDiff = trafficDiff * 1073741824;
|
||||||
this.defaultKey = msg.obj.defaultKey;
|
this.defaultCert = defaultCert;
|
||||||
|
this.defaultKey = defaultKey;
|
||||||
|
this.tgBotEnable = tgBotEnable;
|
||||||
|
this.subSettings = {
|
||||||
|
enable : subEnable,
|
||||||
|
port: subPort,
|
||||||
|
path: subPath,
|
||||||
|
domain: subDomain,
|
||||||
|
tls: subTLS
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
this.inbounds.splice(0);
|
this.inbounds.splice(0);
|
||||||
@@ -362,11 +392,15 @@
|
|||||||
to_inbound = dbInbound.toInbound()
|
to_inbound = dbInbound.toInbound()
|
||||||
this.inbounds.push(to_inbound);
|
this.inbounds.push(to_inbound);
|
||||||
this.dbInbounds.push(dbInbound);
|
this.dbInbounds.push(dbInbound);
|
||||||
if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol)) {
|
if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(inbound.protocol)) {
|
||||||
this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound);
|
this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.searchInbounds(this.searchKey);
|
if(this.enableFilter){
|
||||||
|
this.filterInbounds();
|
||||||
|
} else {
|
||||||
|
this.searchInbounds(this.searchKey);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound, inbound) {
|
getClientCounts(dbInbound, inbound) {
|
||||||
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
||||||
@@ -424,6 +458,38 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
filterInbounds() {
|
||||||
|
if (ObjectUtil.isEmpty(this.filterBy)) {
|
||||||
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
} else {
|
||||||
|
this.searchedInbounds.splice(0, this.searchedInbounds.length);
|
||||||
|
this.dbInbounds.forEach(inbound => {
|
||||||
|
const newInbound = new DBInbound(inbound);
|
||||||
|
const inboundSettings = JSON.parse(inbound.settings);
|
||||||
|
if (this.clientCount[inbound.id] && this.clientCount[inbound.id].hasOwnProperty(this.filterBy)){
|
||||||
|
const list = this.clientCount[inbound.id][this.filterBy];
|
||||||
|
if (list.length > 0) {
|
||||||
|
const filteredSettings = { "clients": [] };
|
||||||
|
inboundSettings.clients.forEach(client => {
|
||||||
|
if (list.includes(client.email)) {
|
||||||
|
filteredSettings.clients.push(client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
newInbound.settings = Inbound.Settings.fromJson(inbound.protocol, filteredSettings);
|
||||||
|
this.searchedInbounds.push(newInbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleFilter(){
|
||||||
|
if(this.enableFilter) {
|
||||||
|
this.searchKey = '';
|
||||||
|
} else {
|
||||||
|
this.filterBy = '';
|
||||||
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
}
|
||||||
|
},
|
||||||
generalActions(action) {
|
generalActions(action) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "export":
|
case "export":
|
||||||
@@ -694,9 +760,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showQrcode(dbInbound, clientIndex) {
|
showQrcode(dbInbound, clientIndex) {
|
||||||
const clientName = JSON.parse(dbInbound.settings).clients[clientIndex].email;
|
qrModal.show('{{ i18n "qrCode"}}', dbInbound, clientIndex);
|
||||||
const link = dbInbound.genLink(clientIndex);
|
|
||||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound, '', clientName);
|
|
||||||
},
|
},
|
||||||
showInfo(dbInbound, index) {
|
showInfo(dbInbound, index) {
|
||||||
infoModal.show(dbInbound, index);
|
infoModal.show(dbInbound, index);
|
||||||
|
|||||||
@@ -19,10 +19,6 @@
|
|||||||
.ant-list-item {
|
.ant-list-item {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
:not(.ant-card-dark)>.ant-tabs-top-bar {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
@@ -35,7 +31,7 @@
|
|||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-tabs default-active-key="1" :class="themeSwitcher.darkCardClass">
|
<a-tabs default-active-key="1" :class="themeSwitcher.darkCardClass" :style="!themeSwitcher.isDarkTheme? 'background: white':''">
|
||||||
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelConfig"}}'>
|
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelConfig"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
||||||
@@ -217,6 +213,11 @@
|
|||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectIPs"}}' v-model="manualDirectIPs"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectIPs"}}' v-model="manualDirectIPs"></setting-list-item>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectDomains"}}' v-model="manualDirectDomains"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectDomains"}}' v-model="manualDirectDomains"></setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||||
|
<a-space direction="horizontal" style="padding: 0 20px">
|
||||||
|
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
||||||
@@ -233,9 +234,6 @@
|
|||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
||||||
<a-space direction="horizontal" style="padding: 0 20px">
|
|
||||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
@@ -257,6 +255,24 @@
|
|||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
||||||
|
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||||
|
{{ i18n "pages.settings.infoDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
|
<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="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></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="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></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.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
||||||
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
||||||
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
||||||
|
</a-list>
|
||||||
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@@ -300,7 +316,6 @@
|
|||||||
},
|
},
|
||||||
ips: {
|
ips: {
|
||||||
local: ["geoip:private"],
|
local: ["geoip:private"],
|
||||||
google: ["geoip:google"],
|
|
||||||
cn: ["geoip:cn"],
|
cn: ["geoip:cn"],
|
||||||
ir: ["geoip:ir"],
|
ir: ["geoip:ir"],
|
||||||
ru: ["geoip:ru"],
|
ru: ["geoip:ru"],
|
||||||
@@ -308,14 +323,9 @@
|
|||||||
domains: {
|
domains: {
|
||||||
ads: [
|
ads: [
|
||||||
"geosite:category-ads-all",
|
"geosite:category-ads-all",
|
||||||
"geosite:category-ads",
|
"ext:iran.dat:ads"
|
||||||
"geosite:google-ads",
|
|
||||||
"geosite:spotify-ads"
|
|
||||||
],
|
],
|
||||||
porn: ["geosite:category-porn"],
|
|
||||||
openai: ["geosite:openai"],
|
|
||||||
google: ["geosite:google"],
|
google: ["geosite:google"],
|
||||||
spotify: ["geosite:spotify"],
|
|
||||||
netflix: ["geosite:netflix"],
|
netflix: ["geosite:netflix"],
|
||||||
cn: [
|
cn: [
|
||||||
"geosite:cn",
|
"geosite:cn",
|
||||||
@@ -329,16 +339,15 @@
|
|||||||
"regexp:.*\\.ir$",
|
"regexp:.*\\.ir$",
|
||||||
"ext:iran.dat:ir",
|
"ext:iran.dat:ir",
|
||||||
"ext:iran.dat:other",
|
"ext:iran.dat:other",
|
||||||
"ext:iran.dat:ads",
|
|
||||||
"geosite:category-ir"
|
"geosite:category-ir"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
familyProtectDNS: {
|
familyProtectDNS: {
|
||||||
"servers": [
|
"servers": [
|
||||||
"1.1.1.2",
|
"1.1.1.3",
|
||||||
"1.0.0.2",
|
"1.0.0.3",
|
||||||
"94.140.14.14",
|
"94.140.14.15",
|
||||||
"94.140.15.15"
|
"94.140.15.16"
|
||||||
],
|
],
|
||||||
"queryStrategy": "UseIPv4"
|
"queryStrategy": "UseIPv4"
|
||||||
},
|
},
|
||||||
@@ -511,7 +520,7 @@
|
|||||||
freedomStrategy: {
|
freedomStrategy: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings) return "AsIs";
|
if (!this.templateSettings) return "AsIs";
|
||||||
freedomOutbound = this.templateSettings.outbounds.find((o) => o.tag === "direct");
|
freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && !o.tag);
|
||||||
if (!freedomOutbound) return "AsIs";
|
if (!freedomOutbound) return "AsIs";
|
||||||
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
||||||
return freedomOutbound.settings.domainStrategy;
|
return freedomOutbound.settings.domainStrategy;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) {
|
|||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, error) {
|
func (s *InboundService) GetClients(inbound *model.Inbound) ([]model.Client, error) {
|
||||||
settings := map[string][]model.Client{}
|
settings := map[string][]model.Client{}
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
if settings == nil {
|
if settings == nil {
|
||||||
@@ -110,7 +110,7 @@ func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (string, error) {
|
func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (string, error) {
|
||||||
clients, err := s.getClients(inbound)
|
clients, err := s.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, err
|
|||||||
return inbound, common.NewError("Duplicate email:", existEmail)
|
return inbound, common.NewError("Duplicate email:", existEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, err := s.getClients(inbound)
|
clients, err := s.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
@@ -249,7 +249,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
||||||
clients, err := s.getClients(data)
|
clients, err := s.GetClients(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -352,7 +352,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
|
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
|
||||||
clients, err := s.getClients(data)
|
clients, err := s.GetClients(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -370,7 +370,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients, err := s.getClients(oldInbound)
|
oldClients, err := s.GetClients(oldInbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -774,7 +774,7 @@ func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTr
|
|||||||
}
|
}
|
||||||
var emails []string
|
var emails []string
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.getClients(inbound)
|
clients, err := s.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Unable to get clients from inbound")
|
logger.Error("Unable to get clients from inbound")
|
||||||
}
|
}
|
||||||
@@ -902,7 +902,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add client traffic row for all clients which has email
|
// Add client traffic row for all clients which has email
|
||||||
modelClients, err := s.getClients(inbounds[inbound_index])
|
modelClients, err := s.GetClients(inbounds[inbound_index])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ var defaultValueMap = map[string]string{
|
|||||||
"tgRunTime": "@daily",
|
"tgRunTime": "@daily",
|
||||||
"tgBotBackup": "false",
|
"tgBotBackup": "false",
|
||||||
"tgCpu": "0",
|
"tgCpu": "0",
|
||||||
|
"subEnable": "false",
|
||||||
|
"subListen": "",
|
||||||
|
"subPort": "2096",
|
||||||
|
"subPath": "sub/",
|
||||||
|
"subDomain": "",
|
||||||
|
"subCertFile": "",
|
||||||
|
"subKeyFile": "",
|
||||||
|
"subUpdates": "12",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -307,6 +315,48 @@ func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
|||||||
return location, nil
|
return location, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubEnable() (bool, error) {
|
||||||
|
return s.getBool("subEnable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubListen() (string, error) {
|
||||||
|
return s.getString("subListen")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubPort() (int, error) {
|
||||||
|
return s.getInt("subPort")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubPath() (string, error) {
|
||||||
|
subPath, err := s.getString("subPath")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(subPath, "/") {
|
||||||
|
subPath = "/" + subPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(subPath, "/") {
|
||||||
|
subPath += "/"
|
||||||
|
}
|
||||||
|
return subPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
|
return s.getString("subDomain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubCertFile() (string, error) {
|
||||||
|
return s.getString("subCertFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubKeyFile() (string, error) {
|
||||||
|
return s.getString("subKeyFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubUpdates() (int, error) {
|
||||||
|
return s.getInt("subUpdates")
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"enable" = "Enable"
|
"enable" = "Enable"
|
||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
"search" = "Search"
|
"search" = "Search"
|
||||||
|
"filter" = "Filter"
|
||||||
|
|
||||||
"loading" = "Loading"
|
"loading" = "Loading"
|
||||||
"second" = "Second"
|
"second" = "Second"
|
||||||
@@ -243,6 +244,24 @@
|
|||||||
"tgNotifyCpuDesc" = "Receive notification if CPU usage exceeds this threshold (unit: %)"
|
"tgNotifyCpuDesc" = "Receive notification if CPU usage exceeds this threshold (unit: %)"
|
||||||
"timeZone" = "Time Zone"
|
"timeZone" = "Time Zone"
|
||||||
"timeZoneDesc" = "Scheduled tasks run according to the time in this time zone."
|
"timeZoneDesc" = "Scheduled tasks run according to the time in this time zone."
|
||||||
|
"subSettings" = "Subscription"
|
||||||
|
"subEnable" = "Enable service"
|
||||||
|
"subEnableDesc" = "Subscription feature with separate configuration"
|
||||||
|
"subListen" = "Listening IP"
|
||||||
|
"subListenDesc" = "Leave blank by default to monitor all IPs"
|
||||||
|
"subPort" = "Subscription Port"
|
||||||
|
"subPortDesc" = "Port number for serving the subscription service must be unused in server"
|
||||||
|
"subCertPath" = "Subscription Certificate Public Key File Path"
|
||||||
|
"subCertPathDesc" = "Fill in an absolute path starting with '/'"
|
||||||
|
"subKeyPath" = "Subscription Certificate Private Key File Path"
|
||||||
|
"subKeyPathDesc" = "Fill in an absolute path starting with '/'"
|
||||||
|
"subPath" = "Subscription URL Root Path"
|
||||||
|
"subPathDesc" = "Must start with '/' and end with '/'"
|
||||||
|
"subDomain" = "Listening Domain"
|
||||||
|
"subDomainDesc" = "Leave blank by default to monitor all domains and IPs"
|
||||||
|
"subUpdates" = "Subscription update intervals"
|
||||||
|
"subUpdatesDesc" = "Interval hours between updates in client application"
|
||||||
|
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "Templates"
|
"title" = "Templates"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"enable" = "فعال"
|
"enable" = "فعال"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"search" = "جستجو"
|
"search" = "جستجو"
|
||||||
|
"filter" = "فیلتر"
|
||||||
|
|
||||||
"loading" = "در حال بروزرسانی.."
|
"loading" = "در حال بروزرسانی.."
|
||||||
"second" = "ثانیه"
|
"second" = "ثانیه"
|
||||||
@@ -242,6 +243,23 @@
|
|||||||
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
||||||
"timeZone" = "منظقه زمانی"
|
"timeZone" = "منظقه زمانی"
|
||||||
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
||||||
|
"subSettings" = "سابسکریپشن"
|
||||||
|
"subEnable" = "فعال کردن سرویس"
|
||||||
|
"subEnableDesc" = "ویژگی سابسکریپشن با پیکربندی جداگانه"
|
||||||
|
"subListen" = "محدودیت آیپی"
|
||||||
|
"subListenDesc" = "برای استفاده از همه آیپی ها به طور پیش فرض خالی بگذارید"
|
||||||
|
"subPort" = "پورت سرویس"
|
||||||
|
"subPortDesc" = "شماره پورت برای ارائه خدمات سابسکریپشن باید خالی باشد"
|
||||||
|
"subCertPath" = "مسیر فایل کلید عمومی گواهی سابسکریپشن"
|
||||||
|
"subCertPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید."
|
||||||
|
"subKeyPath" = "مسیر فایل کلید خصوصی گواهی سابسکریپشن"
|
||||||
|
"subKeyPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید."
|
||||||
|
"subPath" = "مسیر ریشه سابسکریپشن"
|
||||||
|
"subPathDesc" = "باید با '/' شروع شود و با '/' ختم شود."
|
||||||
|
"subDomain" = "دامنه مخصوص سابسکریپشن"
|
||||||
|
"subDomainDesc" = "برای نظارت بر همه دامنه ها و آیپی ها به طور پیش فرض خالی بگذارید"
|
||||||
|
"subUpdates" = "فاصله به روز رسانی های سابسکریپشن"
|
||||||
|
"subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "الگوها"
|
"title" = "الگوها"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"enable" = "включить"
|
"enable" = "включить"
|
||||||
"protocol" = "протокол"
|
"protocol" = "протокол"
|
||||||
"search" = "поиск"
|
"search" = "поиск"
|
||||||
|
"filter" = "Фильтр"
|
||||||
|
|
||||||
"loading" = "загрузка"
|
"loading" = "загрузка"
|
||||||
"second" = "секунда"
|
"second" = "секунда"
|
||||||
@@ -243,6 +244,23 @@
|
|||||||
"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (значение:%)"
|
"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (значение:%)"
|
||||||
"timeZone" = "Временная зона"
|
"timeZone" = "Временная зона"
|
||||||
"timeZoneDesc" = "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе. Перезагрузите панель для применения настроек"
|
"timeZoneDesc" = "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе. Перезагрузите панель для применения настроек"
|
||||||
|
"subSettings" = "Подписка"
|
||||||
|
"subEnable" = "Включить службу"
|
||||||
|
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
||||||
|
"subListen" = "Прослушивание IP"
|
||||||
|
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
|
||||||
|
"subPort" = "Порт подписки"
|
||||||
|
"subPortDesc" = "Номер порта для обслуживания службы подписки не должен использоваться на сервере"
|
||||||
|
"subCertPath" = "Путь к файлу открытого ключа сертификата подписки"
|
||||||
|
"subCertPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
|
||||||
|
"subKeyPath" = "Путь к файлу закрытого ключа сертификата подписки"
|
||||||
|
"subKeyPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
|
||||||
|
"subPath" = "Корневой путь URL-адреса подписки"
|
||||||
|
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
||||||
|
"subDomain" = "Домен прослушивания"
|
||||||
|
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все домены и IP-адреса"
|
||||||
|
"subUpdates" = "Интервалы обновления подписки"
|
||||||
|
"subUpdatesDesc" = "Часовой интервал между обновлениями в клиентском приложении"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "Шаблоны"
|
"title" = "Шаблоны"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"enable" = "启用"
|
"enable" = "启用"
|
||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
"search" = "搜尋"
|
"search" = "搜尋"
|
||||||
|
"filter" = "过滤器"
|
||||||
|
|
||||||
"loading" = "加载中"
|
"loading" = "加载中"
|
||||||
"second" = "秒"
|
"second" = "秒"
|
||||||
@@ -243,6 +244,23 @@
|
|||||||
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
||||||
"timeZone" = "时区"
|
"timeZone" = "时区"
|
||||||
"timeZoneDesc" = "定时任务按照该时区的时间运行"
|
"timeZoneDesc" = "定时任务按照该时区的时间运行"
|
||||||
|
"subSettings" = "订阅"
|
||||||
|
"subEnable" = "启用服务"
|
||||||
|
"subEnableDesc" = "具有单独配置的订阅功能"
|
||||||
|
"subListen" = "监听IP"
|
||||||
|
"subListenDesc" = "留空默认监听所有IP"
|
||||||
|
"subPort" = "订阅端口"
|
||||||
|
"subPortDesc" = "服务订阅服务的端口号必须在服务器中未使用"
|
||||||
|
"subCertPath" = "订阅证书公钥文件路径"
|
||||||
|
"subCertPathDesc" = "填写以'/'开头的绝对路径"
|
||||||
|
"subKeyPath" = "订阅证书私钥文件路径"
|
||||||
|
"subKeyPathDesc" = "填写以'/'开头的绝对路径"
|
||||||
|
"subPath" = "订阅 URL 根路径"
|
||||||
|
"subPathDesc" = "必须以'/'开始并以'/'结束"
|
||||||
|
"subDomain" = "监听域"
|
||||||
|
"subDomainDesc" = "留空默认监控所有域名和IP"
|
||||||
|
"subUpdates" = "订阅更新间隔"
|
||||||
|
"subUpdatesDesc" = "客户端应用程序更新之间的间隔时间"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "模板"
|
"title" = "模板"
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ type Server struct {
|
|||||||
server *controller.ServerController
|
server *controller.ServerController
|
||||||
xui *controller.XUIController
|
xui *controller.XUIController
|
||||||
api *controller.APIController
|
api *controller.APIController
|
||||||
sub *controller.SUBController
|
|
||||||
|
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
@@ -209,7 +208,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
s.server = controller.NewServerController(g)
|
s.server = controller.NewServerController(g)
|
||||||
s.xui = controller.NewXUIController(g)
|
s.xui = controller.NewXUIController(g)
|
||||||
s.api = controller.NewAPIController(g)
|
s.api = controller.NewAPIController(g)
|
||||||
s.sub = controller.NewSUBController(g)
|
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
@@ -276,8 +274,6 @@ func (s *Server) initI18n(engine *gin.Engine) error {
|
|||||||
engine.FuncMap["i18n"] = I18n
|
engine.FuncMap["i18n"] = I18n
|
||||||
|
|
||||||
engine.Use(func(c *gin.Context) {
|
engine.Use(func(c *gin.Context) {
|
||||||
//accept := c.GetHeader("Accept-Language")
|
|
||||||
|
|
||||||
var lang string
|
var lang string
|
||||||
|
|
||||||
if cookie, err := c.Request.Cookie("lang"); err == nil {
|
if cookie, err := c.Request.Cookie("lang"); err == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user