all adjustments
This commit is contained in:
+108
-10
@@ -1,9 +1,11 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"s-ui/logger"
|
||||
"s-ui/service"
|
||||
"s-ui/util"
|
||||
"s-ui/util/common"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -17,6 +19,8 @@ type APIHandler struct {
|
||||
service.ClientService
|
||||
service.TlsService
|
||||
service.InboundService
|
||||
service.OutboundService
|
||||
service.EndpointService
|
||||
service.PanelService
|
||||
service.StatsService
|
||||
service.ServerService
|
||||
@@ -43,6 +47,7 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
||||
var err error
|
||||
action := c.Param("postAction")
|
||||
remoteIP := getRemoteIp(c)
|
||||
loginUser := GetLoginUser(c)
|
||||
|
||||
switch action {
|
||||
case "login":
|
||||
@@ -79,13 +84,21 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
||||
jsonMsg(c, "", err)
|
||||
}
|
||||
case "save":
|
||||
loginUser := GetLoginUser(c)
|
||||
data := map[string]string{}
|
||||
err = c.ShouldBind(&data)
|
||||
if err == nil {
|
||||
err = a.ConfigService.SaveChanges(data, loginUser)
|
||||
obj := c.Request.FormValue("object")
|
||||
act := c.Request.FormValue("action")
|
||||
data := c.Request.FormValue("data")
|
||||
userLinks := c.Request.FormValue("userLinks")
|
||||
outJsons := c.Request.FormValue("outJsons")
|
||||
err = a.ConfigService.Save(obj, act, json.RawMessage(data), json.RawMessage(userLinks), json.RawMessage(outJsons), loginUser)
|
||||
if err != nil {
|
||||
jsonMsg(c, "save", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "save", err)
|
||||
err = a.loadPartialData(c, obj, len(outJsons) > 5, len(userLinks) > 5)
|
||||
if err != nil {
|
||||
jsonMsg(c, obj, err)
|
||||
}
|
||||
return
|
||||
case "restartApp":
|
||||
err = a.PanelService.RestartPanel(3)
|
||||
jsonMsg(c, "restartApp", err)
|
||||
@@ -97,7 +110,7 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
||||
result, _, err := util.GetOutbound(link, 0)
|
||||
jsonObj(c, result, err)
|
||||
default:
|
||||
jsonMsg(c, "API call", nil)
|
||||
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +132,12 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
jsonObj(c, data, nil)
|
||||
case "inbounds", "outbounds", "endpoints", "tls", "clients", "config":
|
||||
err := a.loadPartialData(c, action, false, false)
|
||||
if err != nil {
|
||||
jsonMsg(c, action, err)
|
||||
}
|
||||
return
|
||||
case "users":
|
||||
users, err := a.UserService.GetUsers()
|
||||
if err != nil {
|
||||
@@ -170,7 +189,7 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
||||
keypair := a.ServerService.GenKeypair(kType, options)
|
||||
jsonObj(c, keypair, nil)
|
||||
default:
|
||||
jsonMsg(c, "API call", nil)
|
||||
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +214,7 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
||||
return "", err
|
||||
}
|
||||
if isUpdated {
|
||||
config, err := a.ConfigService.GetConfig()
|
||||
config, err := a.SettingService.GetConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -211,14 +230,24 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
outbounds, err := a.OutboundService.GetAll()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
endpoints, err := a.EndpointService.GetAll()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data["config"] = *config
|
||||
data["config"] = json.RawMessage(config)
|
||||
data["clients"] = clients
|
||||
data["tls"] = tlsConfigs
|
||||
data["inbounds"] = inbounds
|
||||
data["outbounds"] = outbounds
|
||||
data["endpoints"] = endpoints
|
||||
data["subURI"] = subURI
|
||||
data["onlines"] = onlines
|
||||
} else {
|
||||
@@ -227,3 +256,72 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (a *APIHandler) loadPartialData(c *gin.Context, obj string, plusInbounds bool, plusClients bool) error {
|
||||
data := make(map[string]interface{}, 0)
|
||||
switch obj {
|
||||
case "inbounds":
|
||||
id := c.Query("id")
|
||||
inbounds, err := a.InboundService.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[obj] = inbounds
|
||||
case "outbounds":
|
||||
outbounds, err := a.OutboundService.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[obj] = outbounds
|
||||
case "endpoints":
|
||||
endpoints, err := a.EndpointService.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[obj] = endpoints
|
||||
case "tls":
|
||||
tlsConfigs, err := a.TlsService.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[obj] = tlsConfigs
|
||||
case "clients":
|
||||
clients, err := a.ClientService.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[obj] = clients
|
||||
case "config":
|
||||
config, err := a.SettingService.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[obj] = json.RawMessage(config)
|
||||
}
|
||||
|
||||
if plusInbounds {
|
||||
inbounds, err := a.InboundService.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data["inbounds"] = inbounds
|
||||
}
|
||||
if plusClients {
|
||||
clients, err := a.ClientService.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data["clients"] = clients
|
||||
}
|
||||
jsonObj(c, data, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APIHandler) postActions(c *gin.Context) (string, json.RawMessage, error) {
|
||||
var data map[string]json.RawMessage
|
||||
err := c.ShouldBind(&data)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return string(data["action"]), data["data"], nil
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
||||
}
|
||||
} else {
|
||||
m.Success = false
|
||||
m.Msg = msg + err.Error()
|
||||
m.Msg = msg + ": " + err.Error()
|
||||
logger.Warning("failed :", err)
|
||||
}
|
||||
c.JSON(http.StatusOK, m)
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ func (a *APP) Init() error {
|
||||
|
||||
a.core = core.NewCore()
|
||||
|
||||
a.cronJob = cronjob.NewCronJob(a.core)
|
||||
a.cronJob = cronjob.NewCronJob()
|
||||
a.webServer = web.NewServer()
|
||||
a.subServer = sub.NewServer()
|
||||
|
||||
@@ -81,7 +81,7 @@ func (a *APP) Start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.configService.StartCore()
|
||||
err = a.configService.StartCore("")
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
@@ -60,9 +60,22 @@ func moveJsonToDb(db *gorm.DB) error {
|
||||
} else {
|
||||
tls_server, _ := json.MarshalIndent(tlsObj, "", " ")
|
||||
if len(tls_server) > 5 {
|
||||
tlsObject := tlsObj.(map[string]interface{})
|
||||
tlsClientObj := map[string]interface{}{}
|
||||
if enabled, ok := tlsObject["enabled"]; ok {
|
||||
tlsClientObj["enabled"] = enabled
|
||||
}
|
||||
if alpn, ok := tlsObject["alpn"]; ok {
|
||||
tlsClientObj["alpn"] = alpn
|
||||
}
|
||||
if sni, ok := tlsObject["server_name"]; ok {
|
||||
tlsClientObj["server_name"] = sni
|
||||
}
|
||||
tls_client, _ := json.MarshalIndent(tlsClientObj, "", " ")
|
||||
newTls := &model.Tls{
|
||||
Name: tag,
|
||||
Server: tls_server,
|
||||
Client: tls_client,
|
||||
}
|
||||
err = db.Create(newTls).Error
|
||||
if err != nil {
|
||||
@@ -76,10 +89,10 @@ func moveJsonToDb(db *gorm.DB) error {
|
||||
var inbData InboundData
|
||||
db.Raw("select id,addrs,out_json from inbound_data where tag = ?", tag).Find(&inbData)
|
||||
if inbData.Id > 0 {
|
||||
inbObj["outJson"] = inbData.OutJson
|
||||
inbObj["out_json"] = inbData.OutJson
|
||||
inbObj["addrs"] = inbData.Addrs
|
||||
} else {
|
||||
inbObj["outJson"] = json.RawMessage("{}")
|
||||
inbObj["out_json"] = json.RawMessage("{}")
|
||||
inbObj["addrs"] = json.RawMessage("[]")
|
||||
}
|
||||
inbJson, _ := json.Marshal(inbObj)
|
||||
|
||||
+12
-16
@@ -109,20 +109,16 @@ func NewBox(options Options) (*Box, error) {
|
||||
defaultLogWriter = io.Discard
|
||||
}
|
||||
var logFactory log.Factory
|
||||
if factory == nil {
|
||||
logFactory, err = NewFactory(log.Options{
|
||||
Context: ctx,
|
||||
Options: sbCommon.PtrValueOrDefault(options.Log),
|
||||
DefaultWriter: defaultLogWriter,
|
||||
BaseTime: createdAt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, common.NewError("create log factory", err)
|
||||
}
|
||||
factory = logFactory
|
||||
} else {
|
||||
logFactory = factory
|
||||
logFactory, err = NewFactory(log.Options{
|
||||
Context: ctx,
|
||||
Options: sbCommon.PtrValueOrDefault(options.Log),
|
||||
DefaultWriter: defaultLogWriter,
|
||||
BaseTime: createdAt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, common.NewError("create log factory", err)
|
||||
}
|
||||
factory = logFactory
|
||||
|
||||
routeOptions := sbCommon.PtrValueOrDefault(options.Route)
|
||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||
@@ -158,7 +154,7 @@ func NewBox(options Options) (*Box, error) {
|
||||
endpointOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, common.NewError("initialize endpoint["+string(i)+"] "+tag, err)
|
||||
return nil, common.NewError("initialize endpoint["+F.ToString(i)+"] "+tag, err)
|
||||
}
|
||||
}
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
@@ -202,7 +198,7 @@ func NewBox(options Options) (*Box, error) {
|
||||
outboundOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, common.NewError("initialize outbound["+string(i)+"] "+tag, err)
|
||||
return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err)
|
||||
}
|
||||
}
|
||||
outboundManager.Initialize(sbCommon.Must1(
|
||||
@@ -368,7 +364,7 @@ func (s *Box) Close() error {
|
||||
close(s.done)
|
||||
}
|
||||
err := sbCommon.Close(
|
||||
s.inbound, s.outbound, s.router, s.connection, s.network,
|
||||
s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.network,
|
||||
)
|
||||
for _, lifecycleService := range s.services {
|
||||
err1 := lifecycleService.Close()
|
||||
|
||||
@@ -58,7 +58,7 @@ func (c *Core) AddOutbound(config []byte) error {
|
||||
factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"),
|
||||
outbound_config.Tag,
|
||||
outbound_config.Type,
|
||||
outbound_config)
|
||||
outbound_config.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func (c *Core) AddEndpoint(config []byte) error {
|
||||
factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"),
|
||||
endpoint_config.Tag,
|
||||
endpoint_config.Type,
|
||||
endpoint_config)
|
||||
endpoint_config.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package cronjob
|
||||
|
||||
import (
|
||||
"s-ui/service"
|
||||
)
|
||||
|
||||
type CheckCoreJob struct {
|
||||
service.ConfigService
|
||||
}
|
||||
|
||||
func NewCheckCoreJob() *CheckCoreJob {
|
||||
return &CheckCoreJob{}
|
||||
}
|
||||
|
||||
func (s *CheckCoreJob) Run() {
|
||||
s.ConfigService.StartCore("")
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package cronjob
|
||||
|
||||
import (
|
||||
"s-ui/core"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
@@ -9,13 +8,10 @@ import (
|
||||
|
||||
type CronJob struct {
|
||||
cron *cron.Cron
|
||||
Core *core.Core
|
||||
}
|
||||
|
||||
func NewCronJob(c *core.Core) *CronJob {
|
||||
return &CronJob{
|
||||
Core: c,
|
||||
}
|
||||
func NewCronJob() *CronJob {
|
||||
return &CronJob{}
|
||||
}
|
||||
|
||||
func (c *CronJob) Start(loc *time.Location, trafficAge int) error {
|
||||
@@ -29,6 +25,8 @@ func (c *CronJob) Start(loc *time.Location, trafficAge int) error {
|
||||
c.cron.AddJob("@every 1m", NewDepleteJob())
|
||||
// Start deleting old stats
|
||||
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
|
||||
// Start core if it is not running
|
||||
c.cron.AddJob("@every 5s", NewCheckCoreJob())
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type DepleteJob struct {
|
||||
service.ConfigService
|
||||
service.ClientService
|
||||
}
|
||||
|
||||
func NewDepleteJob() *DepleteJob {
|
||||
@@ -14,7 +14,7 @@ func NewDepleteJob() *DepleteJob {
|
||||
}
|
||||
|
||||
func (s *DepleteJob) Run() {
|
||||
err := s.ConfigService.DepleteClients()
|
||||
err := s.ClientService.DepleteClients()
|
||||
if err != nil {
|
||||
logger.Warning("Disable depleted users failed: ", err)
|
||||
return
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package model
|
||||
|
||||
import "encoding/json"
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Endpoint struct {
|
||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Type string `json:"type" form:"type"`
|
||||
Tag string `json:"tag" form:"tag"`
|
||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||
Options json.RawMessage `json:"-" form:"-"`
|
||||
}
|
||||
|
||||
@@ -17,10 +19,10 @@ func (o *Endpoint) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
// Extract fixed fields and store the rest in Options
|
||||
if val, exists := raw["id"]; exists {
|
||||
o.Id = val.(uint)
|
||||
delete(raw, "id")
|
||||
if val, exists := raw["id"].(float64); exists {
|
||||
o.Id = uint(val)
|
||||
}
|
||||
delete(raw, "id")
|
||||
o.Type, _ = raw["type"].(string)
|
||||
delete(raw, "type")
|
||||
o.Tag = raw["tag"].(string)
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
type Inbound struct {
|
||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Type string `json:"type" form:"type"`
|
||||
Tag string `json:"tag" form:"tag"`
|
||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||
|
||||
// Foreign key to tls table
|
||||
TlsId uint `json:"tls_id" form:"tls_id"`
|
||||
Tls *Tls `json:"tls" form:"tls" gorm:"foreignKey:TlsId;references:Id"`
|
||||
|
||||
Addrs json.RawMessage `json:"addrs" form:"addrs"`
|
||||
OutJson json.RawMessage `json:"outJson" form:"outJson"`
|
||||
OutJson json.RawMessage `json:"out_json" form:"out_json"`
|
||||
Options json.RawMessage `json:"-" form:"-"`
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ func (i *Inbound) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
// Extract fixed fields and store the rest in Options
|
||||
if val, exists := raw["id"].(uint); exists {
|
||||
i.Id = val
|
||||
delete(raw, "id")
|
||||
if val, exists := raw["id"].(float64); exists {
|
||||
i.Id = uint(val)
|
||||
}
|
||||
delete(raw, "id")
|
||||
i.Type, _ = raw["type"].(string)
|
||||
delete(raw, "type")
|
||||
i.Tag, _ = raw["tag"].(string)
|
||||
@@ -48,8 +48,8 @@ func (i *Inbound) UnmarshalJSON(data []byte) error {
|
||||
delete(raw, "addrs")
|
||||
|
||||
// OutJson
|
||||
i.OutJson, _ = json.MarshalIndent(raw["outJson"], "", " ")
|
||||
delete(raw, "outJson")
|
||||
i.OutJson, _ = json.MarshalIndent(raw["out_json"], "", " ")
|
||||
delete(raw, "out_json")
|
||||
|
||||
// Remaining fields
|
||||
i.Options, err = json.MarshalIndent(raw, "", " ")
|
||||
@@ -79,3 +79,25 @@ func (i Inbound) MarshalJSON() ([]byte, error) {
|
||||
|
||||
return json.Marshal(combined)
|
||||
}
|
||||
|
||||
func (i Inbound) MarshalFull() (*map[string]interface{}, error) {
|
||||
combined := make(map[string]interface{})
|
||||
combined["id"] = i.Id
|
||||
combined["type"] = i.Type
|
||||
combined["tag"] = i.Tag
|
||||
combined["tls_id"] = i.TlsId
|
||||
combined["addrs"] = i.Addrs
|
||||
combined["out_json"] = i.OutJson
|
||||
|
||||
if i.Options != nil {
|
||||
var restFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(i.Options, &restFields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range restFields {
|
||||
combined[k] = v
|
||||
}
|
||||
}
|
||||
return &combined, nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import "encoding/json"
|
||||
type Outbound struct {
|
||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Type string `json:"type" form:"type"`
|
||||
Tag string `json:"tag" form:"tag"`
|
||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||
Options json.RawMessage `json:"-" form:"-"`
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ func (o *Outbound) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
// Extract fixed fields and store the rest in Options
|
||||
if val, exists := raw["id"]; exists {
|
||||
o.Id = val.(uint)
|
||||
delete(raw, "id")
|
||||
if val, exists := raw["id"].(float64); exists {
|
||||
o.Id = uint(val)
|
||||
}
|
||||
delete(raw, "id")
|
||||
o.Type, _ = raw["type"].(string)
|
||||
delete(raw, "type")
|
||||
o.Tag = raw["tag"].(string)
|
||||
|
||||
+8
-8
@@ -7,8 +7,8 @@ require (
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sagernet/sing v0.6.0-beta.8
|
||||
github.com/sagernet/sing-box v1.11.0-beta.11
|
||||
github.com/sagernet/sing v0.6.0-beta.9
|
||||
github.com/sagernet/sing-box v1.11.0-beta.19
|
||||
github.com/sagernet/sing-dns v0.4.0-beta.1
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
@@ -88,11 +88,11 @@ require (
|
||||
github.com/sagernet/quic-go v0.48.2-beta.1 // indirect
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
|
||||
github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect
|
||||
github.com/sagernet/sing-quic v0.4.0-alpha.4 // indirect
|
||||
github.com/sagernet/sing-quic v0.4.0-beta.2 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.6 // indirect
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f // indirect
|
||||
github.com/sagernet/sing-vmess v0.2.0-beta.1 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
||||
github.com/sagernet/utls v1.6.7 // indirect
|
||||
@@ -110,13 +110,13 @@ require (
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.11.0 // indirect
|
||||
golang.org/x/crypto v0.29.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/net v0.31.0 // indirect
|
||||
golang.org/x/sync v0.9.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
|
||||
@@ -174,16 +174,24 @@ github.com/sagernet/sing v0.6.0-beta.5 h1:RD2j8WmJsvAbbBkAlJWaiYmnd+v/JohBiweoew
|
||||
github.com/sagernet/sing v0.6.0-beta.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.0-beta.8 h1:PoxDdN7y8D4oImT3cQ05Sq1ZYnYsJberkUkIEHIGwWE=
|
||||
github.com/sagernet/sing v0.6.0-beta.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.0-beta.9 h1:P8lKa5hN53fRNAVCIKy5cWd6/kLO5c4slhdsfehSmHs=
|
||||
github.com/sagernet/sing v0.6.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-box v1.11.0-beta.6 h1:MPdL2Yem/xM0RhejCO7krYvl1Zbd1zkSjKluKpHnHPQ=
|
||||
github.com/sagernet/sing-box v1.11.0-beta.6/go.mod h1:6dO5V0A37cLlhvKnxCmZinSpZXz7ZSk11x3rgI+xH1I=
|
||||
github.com/sagernet/sing-box v1.11.0-beta.11 h1:bVR0n3oQ3hGcuc/CSS7axsOeRNCRlCGkYVOKl0wxbsw=
|
||||
github.com/sagernet/sing-box v1.11.0-beta.11/go.mod h1:GZnZUzUHZ6Bgm7D/i8unNORv3537u1s03tLXFdxCRpg=
|
||||
github.com/sagernet/sing-box v1.11.0-beta.15 h1:oWcs/PHgKaeWKbTfgz/020KEVvDqQv/tQWe7zpyktkc=
|
||||
github.com/sagernet/sing-box v1.11.0-beta.15/go.mod h1:+QZDsF4HkdiGcMfz+JNOfONLh9CnZjIwJJQNWEzhiaQ=
|
||||
github.com/sagernet/sing-box v1.11.0-beta.19 h1:uL2xlXpz4t7BduLbXiLe5QqpyiMhvNNRThBzhTJ4p00=
|
||||
github.com/sagernet/sing-box v1.11.0-beta.19/go.mod h1:UXUN/lwRT9mAM8PK7upPOwgqooOV2vU+CcjBfwT1rYg=
|
||||
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
|
||||
github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
|
||||
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
|
||||
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
|
||||
github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ=
|
||||
github.com/sagernet/sing-quic v0.4.0-alpha.4/go.mod h1:h5RkKTmUhudJKzK7c87FPXD5w1bJjVyxMN9+opZcctA=
|
||||
github.com/sagernet/sing-quic v0.4.0-beta.2 h1:ikoQ7zTR0o/2rlI5H5FeNC0j5bQJJHb1uoyXFRu3yGk=
|
||||
github.com/sagernet/sing-quic v0.4.0-beta.2/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||
@@ -194,6 +202,10 @@ github.com/sagernet/sing-tun v0.6.0-beta.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.6 h1:xaIHoH78MqTSvZqQ4SQto8pC1A+X4qXReDRNaC8DQeI=
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.6/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.7 h1:FCSX8oGBqb0H57AAvfGeeH/jMGYWCOg6XWkN/oeES+0=
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.7/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f h1:dTnXP0e3LbSa4EpUmuOGhllanKPei4vPKfzlLvk76Pc=
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
|
||||
github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
@@ -254,6 +266,8 @@ golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
@@ -264,6 +278,8 @@ golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -274,13 +290,18 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -26,7 +26,7 @@ func InitLogger(level logging.Level) {
|
||||
|
||||
backend, err = logging.NewSyslogBackend("")
|
||||
if err != nil {
|
||||
println("Unable to use syslog: " + err.Error())
|
||||
fmt.Println("Unable to use syslog: " + err.Error())
|
||||
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||
}
|
||||
if config.IsSystemd() && err != nil {
|
||||
|
||||
+109
-23
@@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type ClientService struct {
|
||||
InboundService
|
||||
}
|
||||
|
||||
func (s *ClientService) GetAll() ([]model.Client, error) {
|
||||
@@ -23,48 +24,117 @@ func (s *ClientService) GetAll() ([]model.Client, error) {
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func (s *ClientService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||
func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage) ([]uint, error) {
|
||||
var err error
|
||||
for _, change := range changes {
|
||||
client := model.Client{}
|
||||
err = json.Unmarshal(change.Obj, &client)
|
||||
var inboundIds []uint
|
||||
|
||||
switch act {
|
||||
case "new", "edit":
|
||||
var client model.Client
|
||||
err = json.Unmarshal(data, &client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(client.Inbounds, &inboundIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tx.Save(&client).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "del":
|
||||
var id uint
|
||||
err = json.Unmarshal(data, &id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var client model.Client
|
||||
err = tx.Where("id = ?", id).First(&client).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(client.Inbounds, &inboundIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tx.Where("id = ?", id).Delete(model.Client{}).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return inboundIds, nil
|
||||
}
|
||||
|
||||
func (s *ClientService) UpdateLinks(tx *gorm.DB, links json.RawMessage) error {
|
||||
var userLinks []interface{}
|
||||
err := json.Unmarshal(links, &userLinks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, userLink := range userLinks {
|
||||
userLinkData, _ := userLink.(map[string]interface{})
|
||||
userId, _ := userLinkData["id"].(float64)
|
||||
links, err := json.MarshalIndent(userLinkData["links"], "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch change.Action {
|
||||
case "new":
|
||||
err = tx.Create(&client).Error
|
||||
case "del":
|
||||
err = tx.Where("id = ?", change.Index).Delete(model.Client{}).Error
|
||||
default:
|
||||
err = tx.Save(client).Error
|
||||
if inbounds, ok := userLinkData["inbounds"]; ok {
|
||||
inbounds, err := json.MarshalIndent(inbounds, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Model(model.Client{}).Where("id = ?", uint(userId)).Update("inbounds", inbounds).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = tx.Model(model.Client{}).Where("id = ?", uint(userId)).Update("links", links).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ClientService) DepleteClients() ([]string, []string, error) {
|
||||
func (s *ClientService) DepleteClients() error {
|
||||
var err error
|
||||
var clients []model.Client
|
||||
var changes []model.Changes
|
||||
var users []string
|
||||
var inboundIds []uint
|
||||
|
||||
now := time.Now().Unix()
|
||||
db := database.GetDB()
|
||||
err = db.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
|
||||
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
if len(inboundIds) > 0 && corePtr.IsRunning() {
|
||||
err1 := s.InboundService.RestartInbounds(tx, inboundIds)
|
||||
if err1 != nil {
|
||||
logger.Error("unable to restart inbounds: ", err1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
dt := time.Now().Unix()
|
||||
var users, inbounds []string
|
||||
for _, client := range clients {
|
||||
logger.Debug("Client ", client.Name, " is going to be disabled")
|
||||
users = append(users, client.Name)
|
||||
var userInbounds []string
|
||||
var userInbounds []uint
|
||||
json.Unmarshal(client.Inbounds, &userInbounds)
|
||||
inbounds = append(inbounds, userInbounds...)
|
||||
inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds)
|
||||
changes = append(changes, model.Changes{
|
||||
DateTime: dt,
|
||||
Actor: "DepleteJob",
|
||||
@@ -76,16 +146,32 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) {
|
||||
|
||||
// Save changes
|
||||
if len(changes) > 0 {
|
||||
err = db.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
|
||||
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return err
|
||||
}
|
||||
err = db.Model(model.Changes{}).Create(&changes).Error
|
||||
err = tx.Model(model.Changes{}).Create(&changes).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return err
|
||||
}
|
||||
LastUpdate = dt
|
||||
}
|
||||
|
||||
return users, inbounds, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// avoid duplicate inboundIds
|
||||
func (s *ClientService) uniqueAppendInboundIds(a []uint, b []uint) []uint {
|
||||
m := make(map[uint]bool)
|
||||
for _, v := range a {
|
||||
m[v] = true
|
||||
}
|
||||
for _, v := range b {
|
||||
m[v] = true
|
||||
}
|
||||
var res []uint
|
||||
for k := range m {
|
||||
res = append(res, k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
+72
-297
@@ -2,12 +2,12 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"s-ui/config"
|
||||
"s-ui/core"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/logger"
|
||||
"s-ui/util/common"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@@ -48,10 +48,13 @@ func (s *ConfigService) InitConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
|
||||
data, err := s.SettingService.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (s *ConfigService) GetConfig(data string) (*SingBoxConfig, error) {
|
||||
var err error
|
||||
if len(data) == 0 {
|
||||
data, err = s.SettingService.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
singboxConfig := SingBoxConfig{}
|
||||
err = json.Unmarshal([]byte(data), &singboxConfig)
|
||||
@@ -74,8 +77,11 @@ func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
|
||||
return &singboxConfig, nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) StartCore() error {
|
||||
singboxConfig, err := s.GetConfig()
|
||||
func (s *ConfigService) StartCore(defaultConfig string) error {
|
||||
if corePtr.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
singboxConfig, err := s.GetConfig(defaultConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,11 +99,19 @@ func (s *ConfigService) StartCore() error {
|
||||
}
|
||||
|
||||
func (s *ConfigService) RestartCore() error {
|
||||
err := s.StartCore()
|
||||
err := s.StopCore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.StartCore()
|
||||
return s.StartCore("")
|
||||
}
|
||||
|
||||
func (s *ConfigService) restartCoreWithConfig(config json.RawMessage) error {
|
||||
err := s.StopCore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.StartCore(string(config))
|
||||
}
|
||||
|
||||
func (s *ConfigService) StopCore() error {
|
||||
@@ -109,220 +123,80 @@ func (s *ConfigService) StopCore() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
||||
func (s *ConfigService) Save(obj string, act string, data json.RawMessage, userLinks json.RawMessage, outJsons json.RawMessage, loginUser string) error {
|
||||
var err error
|
||||
var clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes
|
||||
if _, ok := changes["clients"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, ok := changes["tls"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["tls"]), &tlsChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, ok := changes["inData"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["inData"]), &inChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, ok := changes["settings"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, ok := changes["config"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["config"]), &configChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var inboundIds []uint
|
||||
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
if len(inboundIds) > 0 && corePtr.IsRunning() {
|
||||
err1 := s.InboundService.RestartInbounds(tx, inboundIds)
|
||||
if err1 != nil {
|
||||
logger.Error("unable to restart inbounds: ", err1)
|
||||
}
|
||||
}
|
||||
// Try to start core if it is not running
|
||||
if !corePtr.IsRunning() {
|
||||
s.StartCore("")
|
||||
}
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
if len(clientChanges) > 0 {
|
||||
err = s.ClientService.Save(tx, clientChanges)
|
||||
switch obj {
|
||||
case "clients":
|
||||
inboundIds, err = s.ClientService.Save(tx, act, data)
|
||||
case "tls":
|
||||
inboundIds, err = s.TlsService.Save(tx, act, data)
|
||||
case "inbounds":
|
||||
err = s.InboundService.Save(tx, act, data)
|
||||
case "outbounds":
|
||||
err = s.OutboundService.Save(tx, act, data)
|
||||
case "endpoints":
|
||||
err = s.EndpointService.Save(tx, act, data)
|
||||
case "config":
|
||||
err = s.SettingService.SaveConfig(tx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.restartCoreWithConfig(data)
|
||||
default:
|
||||
return common.NewError("unknown object: ", obj)
|
||||
}
|
||||
if len(tlsChanges) > 0 {
|
||||
err = s.TlsService.Save(tx, tlsChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if len(inChanges) > 0 {
|
||||
// err = s.InDataService.Save(tx, inChanges)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
if len(settingChanges) > 0 {
|
||||
err = s.SettingService.Save(tx, settingChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
needRestart := false
|
||||
if len(configChanges) > 0 {
|
||||
singboxConfig, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newConfig := *singboxConfig
|
||||
for _, change := range configChanges {
|
||||
rawObject := change.Obj
|
||||
switch change.Key {
|
||||
case "all":
|
||||
err = json.Unmarshal(rawObject, &newConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRestart = true
|
||||
case "log":
|
||||
newConfig.Log = rawObject
|
||||
needRestart = true
|
||||
case "dns":
|
||||
newConfig.Dns = rawObject
|
||||
needRestart = true
|
||||
case "ntp":
|
||||
newConfig.Ntp = rawObject
|
||||
needRestart = true
|
||||
case "route":
|
||||
newConfig.Route = rawObject
|
||||
needRestart = true
|
||||
case "experimental":
|
||||
newConfig.Experimental = rawObject
|
||||
needRestart = true
|
||||
case "inbounds":
|
||||
if change.Action == "edit" {
|
||||
var object map[string]interface{}
|
||||
err = json.Unmarshal(newConfig.Inbounds[change.Index], &object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag, ok := object["tag"].(string); ok {
|
||||
err = corePtr.RemoveInbound(tag)
|
||||
if err == nil {
|
||||
err = corePtr.AddInbound(rawObject)
|
||||
if err != nil {
|
||||
needRestart = true
|
||||
}
|
||||
} else {
|
||||
needRestart = true
|
||||
}
|
||||
} else {
|
||||
needRestart = true
|
||||
}
|
||||
newConfig.Inbounds[change.Index] = rawObject
|
||||
} else if change.Action == "del" {
|
||||
var object map[string]interface{}
|
||||
err = json.Unmarshal(newConfig.Inbounds[change.Index], &object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag, ok := object["tag"].(string); ok {
|
||||
err = corePtr.RemoveInbound(tag)
|
||||
if err != nil {
|
||||
needRestart = true
|
||||
}
|
||||
} else {
|
||||
needRestart = true
|
||||
}
|
||||
newConfig.Inbounds = append(newConfig.Inbounds[:change.Index], newConfig.Inbounds[change.Index+1:]...)
|
||||
} else {
|
||||
newConfig.Inbounds = append(newConfig.Inbounds, rawObject)
|
||||
err = corePtr.AddInbound(rawObject)
|
||||
if err != nil {
|
||||
logger.Debug(err)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
case "outbounds":
|
||||
if change.Action == "edit" {
|
||||
var object map[string]interface{}
|
||||
err = json.Unmarshal(newConfig.Outbounds[change.Index], &object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag, ok := object["tag"].(string); ok {
|
||||
err = corePtr.RemoveOutbound(tag)
|
||||
if err == nil {
|
||||
err = corePtr.AddOutbound(rawObject)
|
||||
if err != nil {
|
||||
needRestart = true
|
||||
}
|
||||
} else {
|
||||
needRestart = true
|
||||
}
|
||||
} else {
|
||||
needRestart = true
|
||||
}
|
||||
newConfig.Outbounds[change.Index] = rawObject
|
||||
} else if change.Action == "del" {
|
||||
var object map[string]interface{}
|
||||
err = json.Unmarshal(newConfig.Outbounds[change.Index], &object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag, ok := object["tag"].(string); ok {
|
||||
err = corePtr.RemoveOutbound(tag)
|
||||
if err != nil {
|
||||
needRestart = true
|
||||
}
|
||||
} else {
|
||||
needRestart = true
|
||||
}
|
||||
newConfig.Outbounds = append(newConfig.Outbounds[:change.Index], newConfig.Outbounds[change.Index+1:]...)
|
||||
} else {
|
||||
err = corePtr.AddOutbound(rawObject)
|
||||
if err != nil {
|
||||
logger.Debug(err)
|
||||
needRestart = true
|
||||
}
|
||||
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = s.Save(&newConfig, needRestart)
|
||||
if len(userLinks) > 0 {
|
||||
err = s.ClientService.UpdateLinks(tx, userLinks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(outJsons) > 0 {
|
||||
err = s.InboundService.UpdateOutJsons(tx, outJsons)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Log changes
|
||||
dt := time.Now().Unix()
|
||||
allChanges := append(clientChanges, settingChanges...)
|
||||
allChanges = append(allChanges, configChanges...)
|
||||
allChanges = append(allChanges, tlsChanges...)
|
||||
allChanges = append(allChanges, inChanges...)
|
||||
if len(allChanges) > 0 {
|
||||
for index := range allChanges {
|
||||
allChanges[index].DateTime = dt
|
||||
allChanges[index].Actor = loginUser
|
||||
}
|
||||
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Create(&model.Changes{
|
||||
DateTime: dt,
|
||||
Actor: loginUser,
|
||||
Key: obj,
|
||||
Action: act,
|
||||
Obj: data,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
LastUpdate = dt
|
||||
LastUpdate = time.Now().Unix()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -345,105 +219,6 @@ func (s *ConfigService) CheckChanges(lu string) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConfigService) Save(singboxConfig *SingBoxConfig, needRestart bool) error {
|
||||
configPath := config.GetBinFolderPath()
|
||||
_, err := os.Stat(configPath + "/config.json")
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(configPath, 01764)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(singboxConfig, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(configPath+"/config.json", data, 0764)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if needRestart {
|
||||
err = s.RestartCore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// s.Controller.Restart()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) DepleteClients() error {
|
||||
users, inboundIds, err := s.ClientService.DepleteClients()
|
||||
if err != nil || len(users) == 0 || len(inboundIds) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
// inbounds, err := s.InboundService.FromIds(inboundIds)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// for inbound_index, inbound := range inbounds {
|
||||
// var inboundJson map[string]interface{}
|
||||
// json.Unmarshal(inbound.Options, &inboundJson)
|
||||
// inbound_users, ok := inboundJson["users"].([]interface{})
|
||||
// if ok {
|
||||
// var updatedUsers []interface{}
|
||||
// for _, user := range inbound_users {
|
||||
// userMap, ok := user.(map[string]interface{})
|
||||
// if ok {
|
||||
// name, exists := userMap["name"].(string)
|
||||
// if exists && s.contains(users, name) {
|
||||
// // Skip the user exists
|
||||
// continue
|
||||
// }
|
||||
// username, exists := userMap["username"].(string)
|
||||
// if exists && s.contains(users, username) {
|
||||
// // Skip the username exists
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// updatedUsers = append(updatedUsers, user)
|
||||
// }
|
||||
// // Exception for Naive and ShadowTLSv3
|
||||
// if len(updatedUsers) == 0 {
|
||||
// if inboundJson["type"].(string) == "naive" ||
|
||||
// (inboundJson["type"].(string) == "shadowtls" &&
|
||||
// inboundJson["version"].(float64) == 3) {
|
||||
// updatedUsers = append(updatedUsers, make(map[string]interface{}))
|
||||
// }
|
||||
// }
|
||||
|
||||
// inboundJson["users"] = updatedUsers
|
||||
// }
|
||||
// modifiedInbound, err := json.MarshalIndent(inboundJson, "", " ")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// inbounds[inbound_index] = modifiedInbound
|
||||
// }
|
||||
|
||||
// err = s.Save(singboxConfig, true)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) contains(slice []string, item string) bool {
|
||||
for _, str := range slice {
|
||||
if str == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
|
||||
c, _ := strconv.Atoi(count)
|
||||
whereString := "`id`>0"
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
|
||||
@@ -10,24 +11,32 @@ import (
|
||||
|
||||
type EndpointService struct{}
|
||||
|
||||
func (o *EndpointService) GetAll() ([]*model.Endpoint, error) {
|
||||
func (o *EndpointService) GetAll() (*[]map[string]interface{}, error) {
|
||||
db := database.GetDB()
|
||||
endpoints := []*model.Endpoint{}
|
||||
err := db.Model(model.Endpoint{}).Scan(&endpoints).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (o *EndpointService) Get(id uint) (*model.Endpoint, error) {
|
||||
db := database.GetDB()
|
||||
endpoint := &model.Endpoint{}
|
||||
err := db.First(endpoint, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var data []map[string]interface{}
|
||||
for _, endpoint := range endpoints {
|
||||
epData := map[string]interface{}{
|
||||
"id": endpoint.Id,
|
||||
"type": endpoint.Type,
|
||||
"tag": endpoint.Tag,
|
||||
}
|
||||
if endpoint.Options != nil {
|
||||
var restFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(endpoint.Options, &restFields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range restFields {
|
||||
epData[k] = v
|
||||
}
|
||||
}
|
||||
data = append(data, epData)
|
||||
}
|
||||
return endpoint, nil
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (o *EndpointService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||
@@ -46,3 +55,55 @@ func (o *EndpointService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||
}
|
||||
return endpointsJson, nil
|
||||
}
|
||||
|
||||
func (s *EndpointService) Save(tx *gorm.DB, action string, data json.RawMessage) error {
|
||||
var err error
|
||||
|
||||
switch action {
|
||||
case "new", "edit":
|
||||
var endpoint model.Endpoint
|
||||
err = endpoint.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if corePtr.IsRunning() {
|
||||
configData, err := endpoint.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if action == "edit" {
|
||||
err = corePtr.RemoveEndpoint(endpoint.Tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = corePtr.AddEndpoint(configData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Save(&endpoint).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "del":
|
||||
var tag string
|
||||
err = json.Unmarshal(data, &tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if corePtr.IsRunning() {
|
||||
err = corePtr.RemoveEndpoint(tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = tx.Where("tag = ?", tag).Delete(model.Endpoint{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+174
-12
@@ -2,22 +2,67 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type InboundService struct{}
|
||||
|
||||
func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
|
||||
func (s *InboundService) Get(ids string) (*[]map[string]interface{}, error) {
|
||||
if ids == "" {
|
||||
return s.GetAll()
|
||||
}
|
||||
return s.getById(ids)
|
||||
}
|
||||
|
||||
func (s *InboundService) getById(ids string) (*[]map[string]interface{}, error) {
|
||||
var inbound []model.Inbound
|
||||
var result []map[string]interface{}
|
||||
db := database.GetDB()
|
||||
inbounds := []map[string]interface{}{}
|
||||
err := db.Model(model.Inbound{}).Select("id, tag, type, address, port, tls_id , count(users) as ucount").Scan(&inbounds).Error
|
||||
err := db.Model(model.Inbound{}).Where("id in ?", strings.Split(ids, ",")).Scan(&inbound).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &inbounds, nil
|
||||
for _, inb := range inbound {
|
||||
inbData, err := inb.MarshalFull()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, *inbData)
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
|
||||
db := database.GetDB()
|
||||
inbounds := []model.Inbound{}
|
||||
err := db.Model(model.Inbound{}).Scan(&inbounds).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []map[string]interface{}
|
||||
for _, inbound := range inbounds {
|
||||
inbData := map[string]interface{}{
|
||||
"id": inbound.Id,
|
||||
"type": inbound.Type,
|
||||
"tag": inbound.Tag,
|
||||
"tls_id": inbound.TlsId,
|
||||
}
|
||||
if inbound.Options != nil {
|
||||
var restFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(inbound.Options, &restFields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inbData["listen"] = restFields["listen"]
|
||||
inbData["listen_port"] = restFields["listen_port"]
|
||||
}
|
||||
data = append(data, inbData)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) FromIds(ids []uint) ([]*model.Inbound, error) {
|
||||
@@ -30,8 +75,91 @@ func (s *InboundService) FromIds(ids []uint) ([]*model.Inbound, error) {
|
||||
return inbounds, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) Save(db *gorm.DB, inbounds []*model.Inbound) error {
|
||||
return db.Save(inbounds).Error
|
||||
func (s *InboundService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
|
||||
var err error
|
||||
|
||||
switch act {
|
||||
case "new", "edit":
|
||||
var inbound model.Inbound
|
||||
err = inbound.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if corePtr.IsRunning() {
|
||||
if act == "edit" {
|
||||
err = corePtr.RemoveInbound(inbound.Tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if inbound.TlsId > 0 {
|
||||
err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
inboundConfig, err := inbound.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = corePtr.AddInbound(inboundConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Save(&inbound).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "del":
|
||||
var tag string
|
||||
err = json.Unmarshal(data, &tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if corePtr.IsRunning() {
|
||||
err = corePtr.RemoveInbound(tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = tx.Where("tag = ?", tag).Delete(model.Inbound{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) UpdateOutJsons(tx *gorm.DB, data json.RawMessage) error {
|
||||
var outJsons []interface{}
|
||||
err := json.Unmarshal(data, &outJsons)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, outJson := range outJsons {
|
||||
outJsonData := outJson.(map[string]interface{})
|
||||
tag := outJsonData["tag"].(string)
|
||||
outJson, err := json.MarshalIndent(outJsonData["out_json"], "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Model(model.Inbound{}).Where("tag = ?", tag).Update("out_json", outJson).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||
@@ -46,12 +174,9 @@ func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch inbound.Type {
|
||||
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
|
||||
inboundJson, err = s.addUsers(db, inboundJson, inbound.Id, inbound.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inboundJson, err = s.addUsers(db, inboundJson, inbound.Id, inbound.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inboundsJson = append(inboundsJson, inboundJson)
|
||||
}
|
||||
@@ -59,11 +184,24 @@ func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||
}
|
||||
|
||||
func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uint, inboundType string) ([]byte, error) {
|
||||
switch inboundType {
|
||||
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
|
||||
break
|
||||
default:
|
||||
return inboundJson, nil
|
||||
}
|
||||
|
||||
var inbound map[string]interface{}
|
||||
err := json.Unmarshal(inboundJson, &inbound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if inboundType == "shadowsocks" {
|
||||
method, _ := inbound["method"].(string)
|
||||
if method == "2022-blake3-aes-128-gcm" {
|
||||
inboundType = "shadowsocks16"
|
||||
}
|
||||
}
|
||||
var users []string
|
||||
err = db.Raw(`SELECT json_extract(clients.config, ?)
|
||||
FROM clients, json_each(clients.inbounds) as je
|
||||
@@ -80,3 +218,27 @@ func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uin
|
||||
inbound["users"] = usersJson
|
||||
return json.Marshal(inbound)
|
||||
}
|
||||
|
||||
func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error {
|
||||
var inbounds []*model.Inbound
|
||||
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", ids).Find(&inbounds).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, inbound := range inbounds {
|
||||
err = corePtr.RemoveInbound(inbound.Tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
inboundConfig, err := inbound.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
|
||||
err = corePtr.AddInbound(inboundConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
|
||||
@@ -10,24 +11,32 @@ import (
|
||||
|
||||
type OutboundService struct{}
|
||||
|
||||
func (o *OutboundService) GetAll() ([]*model.Outbound, error) {
|
||||
func (o *OutboundService) GetAll() (*[]map[string]interface{}, error) {
|
||||
db := database.GetDB()
|
||||
outbounds := []*model.Outbound{}
|
||||
err := db.Model(model.Outbound{}).Scan(&outbounds).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return outbounds, nil
|
||||
}
|
||||
|
||||
func (o *OutboundService) Get(id uint) (*model.Outbound, error) {
|
||||
db := database.GetDB()
|
||||
outbound := &model.Outbound{}
|
||||
err := db.First(outbound, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var data []map[string]interface{}
|
||||
for _, outbound := range outbounds {
|
||||
outData := map[string]interface{}{
|
||||
"id": outbound.Id,
|
||||
"type": outbound.Type,
|
||||
"tag": outbound.Tag,
|
||||
}
|
||||
if outbound.Options != nil {
|
||||
var restFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(outbound.Options, &restFields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range restFields {
|
||||
outData[k] = v
|
||||
}
|
||||
}
|
||||
data = append(data, outData)
|
||||
}
|
||||
return outbound, nil
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (o *OutboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||
@@ -46,3 +55,55 @@ func (o *OutboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||
}
|
||||
return outboundsJson, nil
|
||||
}
|
||||
|
||||
func (s *OutboundService) Save(tx *gorm.DB, action string, data json.RawMessage) error {
|
||||
var err error
|
||||
|
||||
switch action {
|
||||
case "new", "edit":
|
||||
var outbound model.Outbound
|
||||
err = outbound.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if corePtr.IsRunning() {
|
||||
configData, err := outbound.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if action == "edit" {
|
||||
err = corePtr.RemoveOutbound(outbound.Tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = corePtr.AddOutbound(configData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Save(&outbound).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "del":
|
||||
var tag string
|
||||
err = json.Unmarshal(data, &tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if corePtr.IsRunning() {
|
||||
err = corePtr.RemoveOutbound(tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = tx.Where("tag = ?", tag).Delete(model.Outbound{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -343,6 +343,14 @@ func (s *SettingService) SetConfig(config string) error {
|
||||
return s.setString("config", config)
|
||||
}
|
||||
|
||||
func (s *SettingService) SaveConfig(tx *gorm.DB, config json.RawMessage) error {
|
||||
configs, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(model.Setting{}).Where("key = ?", "config").Update("value", string(configs)).Error
|
||||
}
|
||||
|
||||
func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||
var err error
|
||||
for _, change := range changes {
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *StatsService) SaveStats() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.Stats, error) {
|
||||
func (s *StatsService) GetStats(resource string, tag string, limit int) ([]model.Stats, error) {
|
||||
var err error
|
||||
var result []model.Stats
|
||||
|
||||
@@ -82,7 +82,11 @@ func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.
|
||||
timeDiff := currentTime - (int64(limit) * 3600)
|
||||
|
||||
db := database.GetDB()
|
||||
err = db.Model(model.Stats{}).Where("resource = ? AND tag = ? AND date_time > ?", resorce, tag, timeDiff).Scan(&result).Error
|
||||
resources := []string{resource}
|
||||
if resource == "endpoint" {
|
||||
resources = []string{"inbound", "outbound"}
|
||||
}
|
||||
err = db.Model(model.Stats{}).Where("resource in ? AND tag = ? AND date_time > ?", resources, tag, timeDiff).Scan(&result).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+37
-15
@@ -4,11 +4,13 @@ import (
|
||||
"encoding/json"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/util/common"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TlsService struct {
|
||||
InboundService
|
||||
}
|
||||
|
||||
func (s *TlsService) GetAll() ([]model.Tls, error) {
|
||||
@@ -22,25 +24,45 @@ func (s *TlsService) GetAll() ([]model.Tls, error) {
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func (s *TlsService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||
func (s *TlsService) Save(tx *gorm.DB, action string, data json.RawMessage) ([]uint, error) {
|
||||
var err error
|
||||
for _, change := range changes {
|
||||
tlsConfig := model.Tls{}
|
||||
err = json.Unmarshal(change.Obj, &tlsConfig)
|
||||
var inboundIds []uint
|
||||
|
||||
switch action {
|
||||
case "new", "edit":
|
||||
var tls model.Tls
|
||||
err = json.Unmarshal(data, &tls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch change.Action {
|
||||
case "new":
|
||||
err = tx.Create(&tlsConfig).Error
|
||||
case "del":
|
||||
err = tx.Where("id = ?", change.Index).Delete(model.Tls{}).Error
|
||||
default:
|
||||
err = tx.Save(tlsConfig).Error
|
||||
return nil, err
|
||||
}
|
||||
err = tx.Save(&tls).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
err = tx.Model(model.Inbound{}).Select("id").Where("tls_id = ?", tls.Id).Scan(&inboundIds).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inboundIds, nil
|
||||
case "del":
|
||||
var id uint
|
||||
err = json.Unmarshal(data, &id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var inboundCount int64
|
||||
err = tx.Model(model.Inbound{}).Where("tls_id = ?", id).Count(&inboundCount).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if inboundCount > 0 {
|
||||
return nil, common.NewError("tls in use")
|
||||
}
|
||||
err = tx.Where("id = ?", id).Delete(model.Tls{}).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -87,27 +87,22 @@ func (j *JsonService) GetJson(subId string, format string) (*string, error) {
|
||||
return &resultStr, nil
|
||||
}
|
||||
|
||||
func (j *JsonService) getData(subId string) (*model.Client, *[]model.InboundData, error) {
|
||||
func (j *JsonService) getData(subId string) (*model.Client, []*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
client := &model.Client{}
|
||||
err := db.Model(model.Client{}).Where("enable = true and name = ?", subId).First(client).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var inbounds []string
|
||||
err = json.Unmarshal(client.Inbounds, &inbounds)
|
||||
var inbounds []*model.Inbound
|
||||
err = db.Model(model.Inbound{}).Where("tag in ?", client.Inbounds).Find(&inbounds).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
inDatas := &[]model.InboundData{}
|
||||
err = db.Model(model.InboundData{}).Where("tag in ?", inbounds).Find(&inDatas).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return client, inDatas, nil
|
||||
return client, inbounds, nil
|
||||
}
|
||||
|
||||
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]model.InboundData) (*[]map[string]interface{}, *[]string, error) {
|
||||
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inbounds []*model.Inbound) (*[]map[string]interface{}, *[]string, error) {
|
||||
var outbounds []map[string]interface{}
|
||||
var configs map[string]interface{}
|
||||
var outTags []string
|
||||
@@ -116,7 +111,7 @@ func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]mode
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, inData := range *inDatas {
|
||||
for _, inData := range inbounds {
|
||||
if len(inData.OutJson) < 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
Generated
+360
-350
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@
|
||||
:label="$t('in.port')"
|
||||
hide-details
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
required
|
||||
v-model.number="inbound.listen_port"></v-text-field>
|
||||
</v-col>
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
hide-details
|
||||
:items="['4','4a','5']"
|
||||
:label="$t('version')"
|
||||
v-model="inData.outJson.version">
|
||||
v-model="inData.out_json.version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="needNetwork">
|
||||
<Network :data="inData.outJson" />
|
||||
<Network :data="inData.out_json" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="needUot">
|
||||
<UoT :data="inData.outJson" />
|
||||
<UoT :data="inData.out_json" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.HTTP">
|
||||
<v-text-field
|
||||
:label="$t('transport.path')"
|
||||
hide-details
|
||||
v-model="inData.outJson.path">
|
||||
v-model="inData.out_json.path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.VMess || type == inTypes.VLESS">
|
||||
@@ -36,14 +36,14 @@
|
||||
hide-details
|
||||
:label="$t('types.vmess.security')"
|
||||
:items="vmessSecurities"
|
||||
v-model="inData.outJson.security">
|
||||
v-model="inData.out_json.security">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="inData.outJson.global_padding" color="primary" :label="$t('types.vmess.globalPadding')" hide-details></v-switch>
|
||||
<v-switch v-model="inData.out_json.global_padding" color="primary" :label="$t('types.vmess.globalPadding')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="inData.outJson.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
||||
<v-switch v-model="inData.out_json.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</template>
|
||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.Hysteria">
|
||||
@@ -52,7 +52,7 @@
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="inData.outJson.recv_window">
|
||||
v-model.number="inData.out_json.recv_window">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<template v-if="type == inTypes.TUIC">
|
||||
@@ -62,16 +62,16 @@
|
||||
label="UDP Relay Mode"
|
||||
:items="['native', 'quic']"
|
||||
clearable
|
||||
@click:clear="delete inData.outJson.udp_relay_mode"
|
||||
v-model="inData.outJson.udp_relay_mode">
|
||||
@click:clear="delete inData.out_json.udp_relay_mode"
|
||||
v-model="inData.out_json.udp_relay_mode">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="UDP Over Stream" v-model="inData.outJson.udp_over_stream" hide-details></v-switch>
|
||||
<v-switch color="primary" label="UDP Over Stream" v-model="inData.out_json.udp_over_stream" hide-details></v-switch>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
<Headers :data="inData.outJson" v-if="type == inTypes.HTTP" />
|
||||
<Headers :data="inData.out_json" v-if="type == inTypes.HTTP" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
@@ -114,8 +114,8 @@ export default {
|
||||
needNetwork():boolean { return this.haveNetwork.includes(this.$props.type) },
|
||||
needUot():boolean { return this.havUoT.includes(this.$props.type) },
|
||||
packet_encoding: {
|
||||
get() { return this.$props.inData.outJson.packet_encoding != undefined ? this.$props.inData.outJson.packet_encoding : 'none'; },
|
||||
set(v:string) { this.$props.inData.outJson.packet_encoding = v != "none" ? v : undefined }
|
||||
get() { return this.$props.inData.out_json.packet_encoding != undefined ? this.$props.inData.out_json.packet_encoding : 'none'; },
|
||||
set(v:string) { this.$props.inData.out_json.packet_encoding = v != "none" ? v : undefined }
|
||||
},
|
||||
},
|
||||
components: { Network, UoT, Headers }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<v-text-field
|
||||
:label="$t('out.addr')"
|
||||
hide-details
|
||||
v-model="data.server">
|
||||
v-model="address">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
@@ -13,7 +13,16 @@
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
v-model="data.server_port">
|
||||
v-model="port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="KeepAlive"
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
v-model="data.persistent_keepalive_interval">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -36,6 +45,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { KeepAlive } from 'vue';
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
@@ -54,6 +65,14 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
address: {
|
||||
get() { return this.$props.data.address },
|
||||
set(v:string) { this.$props.data.address = v.length > 0 ? v : undefined }
|
||||
},
|
||||
port: {
|
||||
get() { return this.$props.data.port },
|
||||
set(v:number) { this.$props.data.port = v > 0 ? v : undefined }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,22 +2,40 @@
|
||||
<v-card subtitle="Wireguard">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="data.private_key" :label="$t('types.wg.privKey')" hide-details></v-text-field>
|
||||
<v-text-field
|
||||
v-model="data.private_key"
|
||||
:label="$t('types.wg.privKey')"
|
||||
append-icon="mdi-key-star"
|
||||
@click:append="newKey()"
|
||||
hide-details>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="data.peer_public_key" :label="$t('types.wg.pubKey')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8" v-if="data.pre_shared_key != undefined">
|
||||
<v-text-field v-model="data.pre_shared_key" :label="$t('types.wg.psk')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="local_ips" :label="$t('types.wg.localIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||
<v-text-field v-model="address" :label="$t('types.wg.localIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.reserved != undefined">
|
||||
<v-text-field v-model="reserved" :label="'Reserved ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('in.port')"
|
||||
hide-details
|
||||
type="number"
|
||||
min=1
|
||||
v-model.number="data.listen_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.udp_timeout != undefined">
|
||||
<v-text-field
|
||||
label="UDP Timeout"
|
||||
hide-details
|
||||
type="number"
|
||||
min=0
|
||||
:suffix="$t('date.m')"
|
||||
v-model.number="udp_timeout">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.workers != undefined">
|
||||
<v-text-field
|
||||
:label="$t('types.wg.worker')"
|
||||
@@ -39,24 +57,16 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
<v-switch v-model="data.system" color="primary" :label="$t('types.wg.sysIf')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.interface_name != undefined">
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.name != undefined">
|
||||
<v-text-field
|
||||
:label="$t('types.wg.ifName')"
|
||||
hide-details
|
||||
v-model.number="data.interface_name">
|
||||
v-model="data.name">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.system_interface" color="primary" :label="$t('types.wg.sysIf')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.gso" color="primary" :label="$t('types.wg.gso')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||
@@ -66,10 +76,7 @@
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionPsk" color="primary" :label="$t('types.wg.psk')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionRsrv" color="primary" label="Reserved" hide-details></v-switch>
|
||||
<v-switch v-model="optionUdp" color="primary" label="UDP Timeout" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionWorker" color="primary" :label="$t('types.wg.worker')" hide-details></v-switch>
|
||||
@@ -80,9 +87,6 @@
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionInterface" color="primary" :label="$t('types.wg.ifName')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionPeers" color="primary" :label="$t('types.wg.multiPeer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
@@ -95,7 +99,7 @@
|
||||
<template v-for="(p, index) in data.peers">
|
||||
<v-card style="margin-top: 1rem;">
|
||||
<v-card-subtitle>
|
||||
{{ $t('types.wg.peer') + ' ' + (index+1) }} <v-icon icon="mdi-delete" @click="data.peers.splice(index,1)" />
|
||||
{{ $t('types.wg.peer') + ' ' + (index+1) }} <v-icon icon="mdi-delete" @click="data.peers.splice(index,1)" v-if="data.peers.length > 1" />
|
||||
</v-card-subtitle>
|
||||
<Peer :data="p" />
|
||||
</v-card>
|
||||
@@ -104,9 +108,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
import Peer from '@/components/WgPeer.vue'
|
||||
import { WgPeer } from '@/types/outbounds'
|
||||
import WgUtil from '@/plugins/wgUtil'
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
@@ -117,13 +120,19 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
addPeer() {
|
||||
this.$props.data.peers.push({server: '', port: ''})
|
||||
this.$props.data.peers.push({
|
||||
address: '',
|
||||
port: this.$props.data.listen_port
|
||||
})
|
||||
},
|
||||
newKey() {
|
||||
this.$props.data.private_key = WgUtil.generateKeypair().privateKey
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionPsk: {
|
||||
get(): boolean { return this.$props.data.pre_shared_key != undefined },
|
||||
set(v:boolean) { this.$props.data.pre_shared_key = v ? "" : undefined }
|
||||
optionUdp: {
|
||||
get(): boolean { return this.$props.data.udp_timeout != undefined },
|
||||
set(v:boolean) { this.$props.data.udp_timeout = v ? "5m" : undefined }
|
||||
},
|
||||
optionRsrv: {
|
||||
get(): boolean { return this.$props.data.reserved != undefined },
|
||||
@@ -138,16 +147,12 @@ export default {
|
||||
set(v:boolean) { this.$props.data.mtu = v ? 1408 : undefined }
|
||||
},
|
||||
optionInterface: {
|
||||
get(): boolean { return this.$props.data.interface_name != undefined },
|
||||
set(v:boolean) { this.$props.data.interface_name = v ? "" : undefined }
|
||||
get(): boolean { return this.$props.data.name != undefined },
|
||||
set(v:boolean) { this.$props.data.name = v ? "" : undefined }
|
||||
},
|
||||
optionPeers: {
|
||||
get(): boolean { return this.$props.data.peers != undefined },
|
||||
set(v:boolean) { this.$props.data.peers = v ? <WgPeer[]>[] : undefined }
|
||||
},
|
||||
local_ips: {
|
||||
get() { return this.$props.data.local_address?.join(',') },
|
||||
set(v:string) { this.$props.data.local_address = v.length > 0 ? v.split(',') : undefined }
|
||||
address: {
|
||||
get() { return this.$props.data.address?.join(',') },
|
||||
set(v:string) { this.$props.data.address = v.length > 0 ? v.split(',') : undefined }
|
||||
},
|
||||
reserved: {
|
||||
get() { return this.$props.data.reserved?.join(',') },
|
||||
@@ -157,7 +162,11 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
udp_timeout: {
|
||||
get() { return this.$props.data.udp_timeout ? parseInt(this.$props.data.udp_timeout.replace('m','')) : 5 },
|
||||
set(v:number) { this.$props.data.udp_timeout = v > 0 ? v + 'm' : '5m' }
|
||||
}
|
||||
},
|
||||
components: { Network, Peer }
|
||||
components: { Peer }
|
||||
}
|
||||
</script>
|
||||
@@ -1,242 +1,25 @@
|
||||
<template>
|
||||
<v-card :subtitle="$t('objects.tls')">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tlsOptional">
|
||||
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.enabled">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('template')"
|
||||
:items="tlsItems"
|
||||
@update:model-value="changeTlsItem($event)"
|
||||
v-model="tlsId">
|
||||
v-model="inbound.tls_id">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-if="tls.enabled && tlsId == 0">
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-btn-toggle v-model="usePath"
|
||||
class="rounded-xl"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
shaped
|
||||
mandatory>
|
||||
<v-btn
|
||||
@click="tls.key=undefined; tls.certificate=undefined"
|
||||
>{{ $t('tls.usePath') }}</v-btn>
|
||||
<v-btn
|
||||
@click="tls.key_path=undefined; tls.certificate_path=undefined"
|
||||
>{{ $t('tls.useText') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="usePath == 0">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('tls.certPath')"
|
||||
hide-details
|
||||
v-model="tls.certificate_path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('tls.keyPath')"
|
||||
hide-details
|
||||
v-model="tls.key_path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-else>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
:label="$t('tls.cert')"
|
||||
hide-details
|
||||
v-model="certText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
:label="$t('tls.key')"
|
||||
hide-details
|
||||
v-model="keyText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.server_name != undefined">
|
||||
<v-text-field
|
||||
label="SNI"
|
||||
hide-details
|
||||
v-model="tls.server_name">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.alpn">
|
||||
<v-select
|
||||
hide-details
|
||||
label="ALPN"
|
||||
multiple
|
||||
:items="alpn"
|
||||
v-model="tls.alpn">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.min_version">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.minVer')"
|
||||
:items="tlsVersions"
|
||||
v-model="tls.min_version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.max_version">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.maxVer')"
|
||||
:items="tlsVersions"
|
||||
v-model="tls.max_version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" md="8" v-if="tls.cipher_suites != undefined">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.cs')"
|
||||
multiple
|
||||
:items="cipher_suites"
|
||||
v-model="tls.cipher_suites">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<v-card-actions v-if="tls.enabled && tlsId == 0">
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('tls.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMinV" color="primary" :label="$t('tls.minVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { i18n } from '@/locales'
|
||||
import { iTls, defaultInTls } from '@/types/inTls'
|
||||
export default {
|
||||
props: ['inbound', 'tlsConfigs', 'tls_id'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
usePath: this.$props.inbound.tls.key == undefined ? 0 : 1,
|
||||
defaults: defaultInTls,
|
||||
alpn: [
|
||||
{ title: "H3", value: 'h3' },
|
||||
{ title: "H2", value: 'h2' },
|
||||
{ title: "Http/1.1", value: 'http/1.1' },
|
||||
],
|
||||
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
|
||||
cipher_suites: [
|
||||
{ title: "RSA-AES128-CBC-SHA", value: "TLS_RSA_WITH_AES_128_CBC_SHA" },
|
||||
{ title: "RSA-AES256-CBC-SHA", value: "TLS_RSA_WITH_AES_256_CBC_SHA" },
|
||||
{ title: "RSA-AES128-GCM-SHA256", value: "TLS_RSA_WITH_AES_128_GCM_SHA256" },
|
||||
{ title: "RSA-AES256-GCM-SHA384", value: "TLS_RSA_WITH_AES_256_GCM_SHA384" },
|
||||
{ title: "AES128-GCM-SHA256", value: "TLS_AES_128_GCM_SHA256" },
|
||||
{ title: "AES256-GCM-SHA384", value: "TLS_AES_256_GCM_SHA384" },
|
||||
{ title: "CHACHA20-POLY1305-SHA256", value: "TLS_CHACHA20_POLY1305_SHA256" },
|
||||
{ title: "ECDHE-ECDSA-AES128-CBC-SHA", value: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },
|
||||
{ title: "ECDHE-ECDSA-AES256-CBC-SHA", value: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },
|
||||
{ title: "ECDHE-RSA-AES128-CBC-SHA", value: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },
|
||||
{ title: "ECDHE-RSA-AES256-CBC-SHA", value: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },
|
||||
{ title: "ECDHE-ECDSA-AES128-GCM-SHA256", value: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },
|
||||
{ title: "ECDHE-ECDSA-AES256-GCM-SHA384", value: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
|
||||
{ title: "ECDHE-RSA-AES128-GCM-SHA256", value: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },
|
||||
{ title: "ECDHE-RSA-AES256-GCM-SHA384", value: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },
|
||||
{ title: "ECDHE-ECDSA-CHACHA20-POLY1305-SHA256", value: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
|
||||
{ title: "ECDHE-RSA-CHACHA20-POLY1305-SHA256", value: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" }
|
||||
]
|
||||
}
|
||||
},
|
||||
props: ['inbound', 'tlsConfigs'],
|
||||
computed: {
|
||||
tls(): iTls {
|
||||
return <iTls> this.$props.inbound.tls
|
||||
},
|
||||
tlsItems(): any[] {
|
||||
return [ { title: i18n.global.t('none'), value: 0 }, ...this.$props.tlsConfigs?.map((t:any) => { return { title: t.name, value: t.id } } )]
|
||||
},
|
||||
tlsId: {
|
||||
get() { return this.tls_id.value?? 0 },
|
||||
set(newValue: boolean) { this.$props.tls_id.value = newValue }
|
||||
},
|
||||
tlsEnable: {
|
||||
get() { return this.tls.enabled?? false },
|
||||
set(newValue: boolean) {
|
||||
this.$props.inbound.tls = newValue ? { enabled: true } : {}
|
||||
this.$props.tls_id.value = 0
|
||||
}
|
||||
},
|
||||
tlsOptional(): boolean {
|
||||
return !['hysteria','hysteria2','tuic','naive'].includes(this.$props.inbound.type)
|
||||
},
|
||||
certText: {
|
||||
get(): string { return this.tls.certificate ? this.tls.certificate.join('\n') : '' },
|
||||
set(newValue:string) { this.tls.certificate = newValue.split('\n') }
|
||||
},
|
||||
keyText: {
|
||||
get(): string { return this.tls.key ? this.tls.key.join('\n') : '' },
|
||||
set(newValue:string) { this.tls.key = newValue.split('\n') }
|
||||
},
|
||||
optionSNI: {
|
||||
get(): boolean { return this.tls.server_name != undefined },
|
||||
set(v:boolean) { this.tls.server_name = v ? '' : undefined }
|
||||
},
|
||||
optionALPN: {
|
||||
get(): boolean { return this.tls.alpn != undefined },
|
||||
set(v:boolean) { this.tls.alpn = v ? defaultInTls.alpn : undefined }
|
||||
},
|
||||
optionMinV: {
|
||||
get(): boolean { return this.tls.min_version != undefined },
|
||||
set(v:boolean) { this.tls.min_version = v ? defaultInTls.min_version : undefined }
|
||||
},
|
||||
optionMaxV: {
|
||||
get(): boolean { return this.tls.max_version != undefined },
|
||||
set(v:boolean) { this.tls.max_version = v ? defaultInTls.max_version : undefined }
|
||||
},
|
||||
optionCS: {
|
||||
get(): boolean { return this.tls.cipher_suites != undefined },
|
||||
set(v:boolean) { this.tls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTlsItem(id: number){
|
||||
if (id>0) {
|
||||
const tlsConfig = this.$props.tlsConfigs?.findLast((t:any) => t.id == id)
|
||||
if (tlsConfig) this.$props.inbound.tls = tlsConfig.server
|
||||
} else {
|
||||
this.$props.inbound.tls = { enabled: this.tls.enabled }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,13 @@
|
||||
<v-icon v-if="isMobile" icon="mdi-menu" @click="$emit('toggleDrawer')" />
|
||||
<span v-else style="width: 24px"></span>
|
||||
<v-app-bar-title :text="$t(<string>route.name)" class="align-center text-center " />
|
||||
<v-btn prepend-icon="mdi-content-save" v-if="stateChange" :text="$t('actions.save')" @click="saveChanges"></v-btn>
|
||||
<v-icon icon="mdi-theme-light-dark" @click="toggleTheme()" style="margin: 0 10px;"></v-icon>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue"
|
||||
import { ref } from "vue"
|
||||
import { useTheme } from "vuetify"
|
||||
import { FindDiff } from "@/plugins/utils"
|
||||
import Data from "@/store/modules/data"
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
defineProps(['isMobile'])
|
||||
@@ -21,27 +18,9 @@ const route = useRoute();
|
||||
const theme = useTheme()
|
||||
const darkMode = ref(localStorage.getItem('theme') == "dark")
|
||||
|
||||
const store = Data()
|
||||
|
||||
const toggleTheme = () => {
|
||||
darkMode.value = !darkMode.value
|
||||
theme.global.name.value = darkMode.value ? "dark" : "light"
|
||||
localStorage.setItem('theme', theme.global.name.value)
|
||||
}
|
||||
|
||||
const saveChanges = () => {
|
||||
store.pushData()
|
||||
}
|
||||
|
||||
const oldData = computed((): any => {
|
||||
return {config: store.oldData.config, clients: store.oldData.clients, tls: store.oldData.tlsConfigs, inData: store.oldData.inData}
|
||||
})
|
||||
|
||||
const newData = computed((): any => {
|
||||
return {config: store.config, clients: store.clients, tls: store.tlsConfigs, inData: store.inData}
|
||||
})
|
||||
|
||||
const stateChange = computed((): any => {
|
||||
return !FindDiff.deepCompare(newData.value,oldData.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -53,6 +53,7 @@ const menu = [
|
||||
{ title: 'pages.inbounds', icon: 'mdi-cloud-download', path: '/inbounds' },
|
||||
{ title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' },
|
||||
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
|
||||
{ title: 'pages.endpoints', icon: 'mdi-cloud-tags', path: '/endpoints' },
|
||||
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
|
||||
{ title: 'pages.tls', icon: 'mdi-certificate', path: '/tls' },
|
||||
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<DatePick :expiry="expDate" @submit="setDate" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="index != -1">
|
||||
<v-row v-if="id > 0">
|
||||
<v-col cols="12" sm="6" md="4" class="d-flex flex-column">
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
@@ -80,11 +80,6 @@
|
||||
></v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-switch v-model="clientStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-window-item>
|
||||
<v-window-item value="t2">
|
||||
<v-row v-for="(value, key) in clientConfig" :key="key">
|
||||
@@ -189,7 +184,7 @@ import DatePick from '@/components/DateTime.vue'
|
||||
import { HumanReadable } from '@/plugins/utils'
|
||||
|
||||
export default {
|
||||
props: ['visible', 'data', 'index', 'inboundTags', 'groups', 'stats'],
|
||||
props: ['visible', 'data', 'id', 'inboundTags', 'groups'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
@@ -206,7 +201,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.index != -1) {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.client = createClient(newData)
|
||||
this.title = "edit"
|
||||
@@ -217,7 +212,6 @@ export default {
|
||||
this.title = "add"
|
||||
this.clientConfig = randomConfigs('client')
|
||||
}
|
||||
this.clientStats = this.$props.stats
|
||||
this.links = this.client.links.filter(l => l.type == 'local')
|
||||
this.extLinks = this.client.links.filter(l => l.type == 'external')
|
||||
this.subLinks = this.client.links.filter(l => l.type == 'sub')
|
||||
@@ -243,8 +237,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
clientInbounds: {
|
||||
get() { return this.client.inbounds.length>0 ? this.client.inbounds.filter(i => this.inboundTags.includes(i)) : [] },
|
||||
set(newValue:string[]) { this.client.inbounds = newValue.length == 0 ? [] : newValue }
|
||||
get() { return this.client.inbounds.length>0 ? this.client.inbounds : [] },
|
||||
set(v:any[]) { this.client.inbounds = v.length == 0 ? [] : v.map(i => i.value) }
|
||||
},
|
||||
expDate: {
|
||||
get() { return this.client.expiry},
|
||||
|
||||
@@ -59,11 +59,6 @@
|
||||
></v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-switch v-model="bulkData.clientStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
@@ -109,7 +104,6 @@ export default {
|
||||
clientInbounds: [],
|
||||
expiry: 0,
|
||||
Volume: 0,
|
||||
clientStats: false,
|
||||
},
|
||||
patterns: [
|
||||
{ title: i18n.global.t("bulk.random"), value: "random" },
|
||||
@@ -129,7 +123,6 @@ export default {
|
||||
clientInbounds: [],
|
||||
expiry: 0,
|
||||
Volume: 0,
|
||||
clientStats: false,
|
||||
}
|
||||
},
|
||||
closeModal() {
|
||||
@@ -157,7 +150,7 @@ export default {
|
||||
group: this.bulkData.group
|
||||
}))
|
||||
}
|
||||
this.$emit('save', this.clients, this.bulkData.clientInbounds, this.bulkData.clientStats)
|
||||
this.$emit('save', this.clients, this.bulkData.clientInbounds)
|
||||
this.resetData() // reset to default
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||
<v-card class="rounded-lg">
|
||||
<v-card-title>
|
||||
{{ $t('actions.' + title) + " " + $t('objects.endpoint') }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||
<v-container style="padding: 0;">
|
||||
<v-tabs
|
||||
v-model="tab"
|
||||
align-tabs="center"
|
||||
>
|
||||
<v-tab value="t1">{{ $t('client.basics') }}</v-tab>
|
||||
<v-tab value="t2">{{ $t('client.external') }}</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="t1">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('type')"
|
||||
:items="Object.keys(epTypes).map((key,index) => ({title: key, value: Object.values(epTypes)[index]}))"
|
||||
v-model="endpoint.type"
|
||||
@update:modelValue="changeType">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="endpoint.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Wireguard v-if="endpoint.type == epTypes.Wireguard" :data="endpoint" />
|
||||
<Dial :dial="endpoint" :outTags="tags" />
|
||||
</v-window-item>
|
||||
<v-window-item value="t2">
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field v-model="link" :label="$t('client.external')" hide-details />
|
||||
</v-col>
|
||||
<v-col cols="12" align="center">
|
||||
<v-btn hide-details variant="tonal" :loading="loading" @click="linkConvert">{{ $t('submit') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="blue-darken-1"
|
||||
variant="text"
|
||||
@click="closeModal"
|
||||
>
|
||||
{{ $t('actions.close') }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="blue-darken-1"
|
||||
variant="text"
|
||||
:loading="loading"
|
||||
@click="saveChanges"
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { EpTypes, createEndpoint } from '@/types/endpoints'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
import Dial from '@/components/Dial.vue'
|
||||
import Wireguard from '@/components/protocols/Wireguard.vue'
|
||||
import HttpUtils from '@/plugins/httputil'
|
||||
import WgUtil from '@/plugins/wgUtil'
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'tags'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
endpoint: createEndpoint("wireguard",{ "tag": "" }),
|
||||
title: "add",
|
||||
tab: "t1",
|
||||
link: "",
|
||||
loading: false,
|
||||
epTypes: EpTypes,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.endpoint = createEndpoint(newData.type, newData)
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||
const randomIPoctet = RandomUtil.randomIntRange(1, 255)
|
||||
this.endpoint = createEndpoint("wireguard",{
|
||||
tag: "wireguard-" + RandomUtil.randomSeq(3),
|
||||
address: ['10.0.0.'+ randomIPoctet.toString() +'/32','fe80::'+ randomIPoctet.toString(16) +'/128'],
|
||||
listen_port: port,
|
||||
private_key: WgUtil.generateKeypair().privateKey,
|
||||
peers: [{
|
||||
public_key: WgUtil.generateKeypair().publicKey,
|
||||
allowed_ips: ['0.0.0.0/0', '::/0']
|
||||
}]
|
||||
})
|
||||
this.title = "add"
|
||||
}
|
||||
this.tab = "t1"
|
||||
},
|
||||
changeType() {
|
||||
// Tag change only in add endpoint
|
||||
const tag = this.$props.id > 0 ? this.endpoint.tag : this.endpoint.type + "-" + RandomUtil.randomSeq(3)
|
||||
// Use previous data
|
||||
const prevConfig = { id: this.endpoint.id, tag: tag ,listen: this.endpoint.listen, listen_port: this.endpoint.listen_port }
|
||||
this.endpoint = createEndpoint(this.endpoint.type, prevConfig)
|
||||
},
|
||||
closeModal() {
|
||||
this.updateData() // reset
|
||||
this.$emit('close')
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.endpoint)
|
||||
this.loading = false
|
||||
},
|
||||
async linkConvert() {
|
||||
if (this.link.length>0){
|
||||
this.loading = true
|
||||
const msg = await HttpUtils.post('api/linkConvert', { link: this.link })
|
||||
this.loading = false
|
||||
if (msg.success) {
|
||||
this.endpoint = msg.obj
|
||||
this.tab = "t1"
|
||||
this.link = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { Dial, Wireguard }
|
||||
}
|
||||
</script>
|
||||
@@ -1,12 +1,18 @@
|
||||
<template>
|
||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||
<v-card class="rounded-lg">
|
||||
<v-dialog transition="dialog-bottom-transition" width="800" @after-enter="updateData">
|
||||
<v-card class="rounded-lg" :loading="loading">
|
||||
<v-card-title>
|
||||
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-skeleton-loader
|
||||
class="mx-auto border"
|
||||
width="95%"
|
||||
type="card, text, divider, list-item-two-line"
|
||||
v-if="loading"
|
||||
></v-skeleton-loader>
|
||||
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||
<v-container style="padding: 0;">
|
||||
<v-container style="padding: 0;" :hidden="loading">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
@@ -45,19 +51,18 @@
|
||||
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
||||
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" />
|
||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="tls_id" />
|
||||
<InTls v-if="HasTls.includes(inbound.type)" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="inbound.tls_id" />
|
||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
||||
<v-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-window-item>
|
||||
<v-window-item value="c">
|
||||
<OutJsonVue :inData="inData" :type="inbound.type" />
|
||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="out" :data="inData.outJson" />
|
||||
<OutJsonVue :inData="inbound" :type="inbound.type" />
|
||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="out" :data="inbound.out_json" />
|
||||
<v-card>
|
||||
<v-card-subtitle>{{ $t('in.multiDomain') }}
|
||||
<v-icon @click="add_addr" icon="mdi-plus"></v-icon>
|
||||
</v-card-subtitle>
|
||||
<template v-for="addr,index in inData.addrs">
|
||||
{{ $t('in.addr') }} #{{ (index+1) }} <v-icon icon="mdi-delete" @click="inData.addrs.splice(index,1)" />
|
||||
<template v-for="addr,index in inbound.addrs">
|
||||
{{ $t('in.addr') }} #{{ (index+1) }} <v-icon icon="mdi-delete" @click="inbound.addrs?.splice(index,1)" />
|
||||
<v-divider></v-divider>
|
||||
<AddrVue :addr="addr" :hasTls="Object.hasOwn(inbound,'tls')" />
|
||||
</template>
|
||||
@@ -79,6 +84,7 @@
|
||||
color="blue-darken-1"
|
||||
variant="text"
|
||||
:loading="loading"
|
||||
:disabled="!validate"
|
||||
@click="saveChanges"
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
@@ -89,8 +95,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { InTypes, createInbound } from '@/types/inbounds'
|
||||
import { Addr, InData } from '@/plugins/inData'
|
||||
import { InTypes, createInbound, Addr } from '@/types/inbounds'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
|
||||
import Listen from '@/components/Listen.vue'
|
||||
@@ -109,19 +114,17 @@ import Multiplex from '@/components/Multiplex.vue'
|
||||
import Transport from '@/components/Transport.vue'
|
||||
import AddrVue from '@/components/Addr.vue'
|
||||
import OutJsonVue from '@/components/OutJson.vue'
|
||||
import Data from '@/store/modules/data'
|
||||
export default {
|
||||
props: ['visible', 'data', 'cData', 'index', 'stats', 'inTags', 'outTags', 'tlsConfigs'],
|
||||
props: ['visible', 'id', 'inTags', 'outTags', 'tlsConfigs'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
inbound: createInbound("direct",{ "tag": "" }),
|
||||
inData: <InData>{},
|
||||
inbound: createInbound("direct",{ id:0, "tag": "" }),
|
||||
title: "add",
|
||||
loading: false,
|
||||
side: "s",
|
||||
inTypes: InTypes,
|
||||
inboundStats: false,
|
||||
tls_id: { value: 0 },
|
||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||
HasInData: [
|
||||
InTypes.SOCKS,
|
||||
@@ -136,56 +139,65 @@ export default {
|
||||
InTypes.TUIC,
|
||||
InTypes.Hysteria2,
|
||||
InTypes.Naive,
|
||||
]
|
||||
],
|
||||
HasTls: [
|
||||
InTypes.HTTP,
|
||||
InTypes.VMess,
|
||||
InTypes.Trojan,
|
||||
InTypes.Naive,
|
||||
InTypes.Hysteria,
|
||||
InTypes.TUIC,
|
||||
InTypes.Hysteria2,
|
||||
InTypes.VLESS,
|
||||
],
|
||||
OnlyTLS: [InTypes.Hysteria, InTypes.Hysteria2, InTypes.TUIC, InTypes.Naive ],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
const inboundArray = await Data().loadInbounds([this.$props.id])
|
||||
this.inbound = inboundArray[0]
|
||||
this.loading = false
|
||||
},
|
||||
updateData() {
|
||||
if (this.$props.index != -1) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.inbound = createInbound(newData.type, newData)
|
||||
this.tls_id.value = this.$props.tlsConfigs?.findLast((t:any) => t.inbounds?.includes(this.inbound.tag))?.id?? 0
|
||||
if (this.HasInData.includes(this.inbound.type)){
|
||||
this.inData = this.$props.cData?.length> 0 ? <InData>JSON.parse(this.$props.cData) : <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||
} else {
|
||||
this.inData = <InData>{id: -1}
|
||||
}
|
||||
if (this.$props.id > 0) {
|
||||
this.loadData()
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||
this.inbound = createInbound("direct",{ tag: "direct-"+port ,listen: "::", listen_port: port })
|
||||
this.tls_id.value = 0
|
||||
this.inbound = createInbound("direct",{ id: 0, tag: "direct-"+port ,listen: "::", listen_port: port })
|
||||
if (this.HasInData.includes(this.inbound.type)){
|
||||
this.inData = <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||
this.inbound.addrs = []
|
||||
this.inbound.out_json = {}
|
||||
} else {
|
||||
this.inData = <InData>{id: -1}
|
||||
delete this.inbound.addrs
|
||||
delete this.inbound.out_json
|
||||
}
|
||||
this.title = "add"
|
||||
this.loading = false
|
||||
}
|
||||
this.inboundStats = this.$props.stats
|
||||
this.side = "s"
|
||||
},
|
||||
changeType() {
|
||||
if (!this.inbound.listen_port) this.inbound.listen_port = RandomUtil.randomIntRange(10000, 60000)
|
||||
// Tag change only in add inbound
|
||||
const tag = this.$props.index != -1 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
||||
const tag = this.$props.id > 0 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
||||
// Use previous data
|
||||
const prevConfig = { tag: tag ,listen: this.inbound.listen?? "::", listen_port: this.inbound.listen_port }
|
||||
const prevConfig = { id: this.inbound.id, tag: tag ,listen: this.inbound.listen?? "::", listen_port: this.inbound.listen_port }
|
||||
this.inbound = createInbound(this.inbound.type, this.inbound.type != this.inTypes.Tun ? prevConfig : { tag: tag })
|
||||
if (this.HasInData.includes(this.inbound.type)){
|
||||
if (this.inData.id == -1) this.inData.id = 0
|
||||
this.inData.addrs = []
|
||||
this.inData.outJson = {}
|
||||
this.inData.tag = tag
|
||||
this.inbound.addrs = []
|
||||
this.inbound.out_json = {}
|
||||
} else {
|
||||
this.inData = <InData>{id: -1}
|
||||
delete this.inbound.addrs
|
||||
delete this.inbound.out_json
|
||||
}
|
||||
this.tls_id.value = 0
|
||||
this.side = "s"
|
||||
},
|
||||
add_addr() {
|
||||
this.inData.addrs.push(<Addr>{ server: location.hostname, server_port: this.inbound.listen_port })
|
||||
this.inbound.addrs?.push(<Addr>{ server: location.hostname, server_port: this.inbound.listen_port })
|
||||
},
|
||||
closeModal() {
|
||||
this.updateData() // reset
|
||||
@@ -193,14 +205,23 @@ export default {
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.inbound, this.inboundStats, this.tls_id.value, this.inData)
|
||||
this.$emit('save', this.inbound)
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
validate() {
|
||||
if (this.inbound == undefined) return false
|
||||
if (this.inbound.tag == "") return false
|
||||
if (this.inbound.listen_port > 65535 || this.inbound.listen_port < 1) return false
|
||||
if (this.OnlyTLS.includes(this.inbound.type) && this.inbound.tls_id == 0) return false
|
||||
return true
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.updateData()
|
||||
this.loading = true
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
<Shadowsocks v-if="outbound.type == outTypes.Shadowsocks" direction="out" :data="outbound" />
|
||||
<Vmess v-if="outbound.type == outTypes.VMess" :data="outbound" />
|
||||
<Trojan v-if="outbound.type == outTypes.Trojan" :data="outbound" />
|
||||
<Wireguard v-if="outbound.type == outTypes.Wireguard" :data="outbound" />
|
||||
<Hysteria v-if="outbound.type == outTypes.Hysteria" direction="out" :data="outbound" />
|
||||
<ShadowTls v-if="outbound.type == outTypes.ShadowTLS" :data="outbound" />
|
||||
<Vless v-if="outbound.type == outTypes.VLESS" :data="outbound" />
|
||||
@@ -69,7 +68,6 @@
|
||||
<OutTLS v-if="Object.hasOwn(outbound,'tls')" :outbound="outbound" />
|
||||
<Multiplex v-if="Object.hasOwn(outbound,'multiplex')" direction="out" :data="outbound" />
|
||||
<Dial v-if="!NoDial.includes(outbound.type)" :dial="outbound" :outTags="tags" />
|
||||
<v-switch v-model="outboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-window-item>
|
||||
<v-window-item value="t2">
|
||||
<v-row>
|
||||
@@ -131,7 +129,7 @@ import Selector from '@/components/protocols/Selector.vue'
|
||||
import UrlTest from '@/components/protocols/UrlTest.vue'
|
||||
import HttpUtils from '@/plugins/httputil'
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'stats', 'tags'],
|
||||
props: ['visible', 'data', 'id', 'tags'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
@@ -141,14 +139,13 @@ export default {
|
||||
link: "",
|
||||
loading: false,
|
||||
outTypes: OutTypes,
|
||||
outboundStats: false,
|
||||
NoDial: [OutTypes.Block, OutTypes.DNS, OutTypes.Selector, OutTypes.URLTest],
|
||||
NoServer: [OutTypes.Direct, OutTypes.Block, OutTypes.DNS, OutTypes.Selector, OutTypes.URLTest, OutTypes.Tor],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.id != -1) {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.outbound = createOutbound(newData.type, newData)
|
||||
this.title = "edit"
|
||||
@@ -158,13 +155,12 @@ export default {
|
||||
this.title = "add"
|
||||
}
|
||||
this.tab = "t1"
|
||||
this.outboundStats = this.$props.stats
|
||||
},
|
||||
changeType() {
|
||||
// Tag change only in add outbound
|
||||
const tag = this.$props.id != -1 ? this.outbound.tag : this.outbound.type + "-" + RandomUtil.randomSeq(3)
|
||||
const tag = this.$props.id > 0 ? this.outbound.tag : this.outbound.type + "-" + RandomUtil.randomSeq(3)
|
||||
// Use previous data
|
||||
const prevConfig = { tag: tag ,listen: this.outbound.listen, listen_port: this.outbound.listen_port }
|
||||
const prevConfig = { id: this.outbound.id, tag: tag ,listen: this.outbound.listen, listen_port: this.outbound.listen_port }
|
||||
this.outbound = createOutbound(this.outbound.type, prevConfig)
|
||||
},
|
||||
closeModal() {
|
||||
@@ -173,7 +169,7 @@ export default {
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.outbound, this.outboundStats)
|
||||
this.$emit('save', this.outbound)
|
||||
this.loading = false
|
||||
},
|
||||
async linkConvert() {
|
||||
|
||||
@@ -298,11 +298,11 @@ import { push } from 'notivue'
|
||||
import { i18n } from '@/locales'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
export default {
|
||||
props: ['visible', 'data', 'index'],
|
||||
props: ['visible', 'data', 'id'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
tls: { id: -1, name: '', inbounds: [], server: <iTls>{ enabled: true }, client: <oTls>{} },
|
||||
tls: { id: 0, name: '', server: <iTls>{ enabled: true }, client: <oTls>{} },
|
||||
title: "add",
|
||||
loading: false,
|
||||
menu: false,
|
||||
@@ -354,15 +354,17 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.index != -1) {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.tls = newData
|
||||
if (this.tls.server == null) this.tls.server = {}
|
||||
if (this.tls.client == null) this.tls.client = {}
|
||||
this.tlsType = newData.server?.reality == undefined ? 0 : 1
|
||||
this.usePath = newData.server?.key == undefined ? 0 : 1
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
this.tls = { id: 0, name: '', inbounds: [], server: {enabled: true}, client: {} }
|
||||
this.tls = { id: 0, name: '', server: {enabled: true}, client: {} }
|
||||
this.tlsType = 0
|
||||
this.usePath = 0
|
||||
this.title = "add"
|
||||
@@ -461,10 +463,10 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
inTls(): iTls {
|
||||
return <iTls> this.tls.server
|
||||
return this.tls.server
|
||||
},
|
||||
outTls(): oTls {
|
||||
return <oTls> this.tls.client
|
||||
return this.tls.client
|
||||
},
|
||||
certText: {
|
||||
get(): string { return this.inTls.certificate ? this.inTls.certificate.join('\n') : '' },
|
||||
@@ -476,11 +478,11 @@ export default {
|
||||
},
|
||||
disableSni: {
|
||||
get() { return this.outTls.disable_sni ?? false },
|
||||
set(v: boolean) { this.outTls.disable_sni = v ? true : undefined }
|
||||
set(v: boolean) { this.tls.client.disable_sni = v ? true : undefined }
|
||||
},
|
||||
insecure: {
|
||||
get() { return this.outTls.insecure ?? false },
|
||||
set(v: boolean) { this.outTls.insecure = v ? true : undefined }
|
||||
set(v: boolean) { this.tls.client.insecure = v ? true : undefined }
|
||||
},
|
||||
server_port: {
|
||||
get() { return this.inTls.reality?.handshake?.server_port ? this.inTls.reality.handshake.server_port : 443 },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { en } from "vuetify/lib/locale/index.mjs";
|
||||
|
||||
export default {
|
||||
message: "Welcome",
|
||||
success: "success",
|
||||
@@ -37,6 +39,7 @@ export default {
|
||||
home: "Home",
|
||||
inbounds: "Inbounds",
|
||||
outbounds: "Outbounds",
|
||||
endpoints: "Endpoints",
|
||||
clients: "Clients",
|
||||
rules: "Rules",
|
||||
tls: "TLS Settings",
|
||||
@@ -75,6 +78,8 @@ export default {
|
||||
inbound: "Inbound",
|
||||
client: "Client",
|
||||
outbound: "Outbound",
|
||||
endpoint: "Endpoint",
|
||||
config: "Config",
|
||||
rule: "Rule",
|
||||
user: "User",
|
||||
tag: "Tag",
|
||||
@@ -228,7 +233,6 @@ export default {
|
||||
worker: "Workers",
|
||||
ifName: "Interface Name",
|
||||
sysIf: "System Interface",
|
||||
gso: "Segmentation Offload",
|
||||
options: "Wireguard Options",
|
||||
multiPeer: "Multi Peer",
|
||||
allowedIp: "Allowed IPs",
|
||||
@@ -394,7 +398,6 @@ export default {
|
||||
download: "Download",
|
||||
volume: "Volume",
|
||||
usage: "Usage",
|
||||
enable: "Enable Statistics",
|
||||
graphTitle: "Traffic Chart",
|
||||
B: "B",
|
||||
KB: "KB",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { config } from "process";
|
||||
|
||||
export default {
|
||||
message: "خوش آمدید",
|
||||
success: "موفق",
|
||||
@@ -37,6 +39,7 @@ export default {
|
||||
home: "خانه",
|
||||
inbounds: "ورودیها",
|
||||
outbounds: "خروجیها",
|
||||
endpoints: "درگاهها",
|
||||
clients: "کاربران",
|
||||
rules: "قوانین",
|
||||
tls: "رمزنگاریها",
|
||||
@@ -75,6 +78,8 @@ export default {
|
||||
inbound: "ورودی",
|
||||
client: "کاربر",
|
||||
outbound: "خروجی",
|
||||
endpoint: "درگاه",
|
||||
config: "پیکربندی",
|
||||
rule: "قانون",
|
||||
user: "کاربر",
|
||||
tag: "برچسب",
|
||||
@@ -227,7 +232,6 @@ export default {
|
||||
worker: "عملگرها",
|
||||
ifName: "نام اینترفیس",
|
||||
sysIf: "استفاده از اینترفیس سیستم",
|
||||
gso: "بارگذاری تقسیمبندی عمومی",
|
||||
options: "گزینههای Wireguard",
|
||||
multiPeer: "چند همتایی",
|
||||
allowedIp: "آدرسهای مجاز",
|
||||
@@ -393,7 +397,6 @@ export default {
|
||||
download: "دانلود",
|
||||
volume: "حجم",
|
||||
usage: "استفاده",
|
||||
enable: "فعال سازی کنترل ترافیک",
|
||||
graphTitle: "نمودار ترافیک",
|
||||
B: "ب",
|
||||
KB: "کب",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { config } from "process";
|
||||
|
||||
export default {
|
||||
message: "Добро пожаловать",
|
||||
success: "успех",
|
||||
@@ -37,6 +39,7 @@ export default {
|
||||
home: "Главная",
|
||||
inbounds: "Входящие",
|
||||
outbounds: "Исходящие",
|
||||
endpoints: "Эндпоинты",
|
||||
clients: "Клиенты",
|
||||
rules: "Правила",
|
||||
tls: "Настройки TLS",
|
||||
@@ -75,6 +78,8 @@ export default {
|
||||
inbound: "Входящий",
|
||||
client: "Клиент",
|
||||
outbound: "Исходящий",
|
||||
endpoint: "Точка входа",
|
||||
config: "Настройки",
|
||||
rule: "Правило",
|
||||
user: "Пользователь",
|
||||
tag: "Тег",
|
||||
@@ -228,7 +233,6 @@ export default {
|
||||
worker: "Работники",
|
||||
ifName: "Имя интерфейса",
|
||||
sysIf: "Системный интерфейс",
|
||||
gso: "Отключение сегментации",
|
||||
options: "Параметры Wireguard",
|
||||
multiPeer: "Множественный пир",
|
||||
allowedIp: "Разрешенные IP",
|
||||
@@ -394,7 +398,6 @@ export default {
|
||||
download: "Скачивание",
|
||||
volume: "Объем",
|
||||
usage: "Использование",
|
||||
enable: "Включить статистику",
|
||||
graphTitle: "График трафика",
|
||||
B: "Б",
|
||||
KB: "КБ",
|
||||
|
||||
@@ -37,6 +37,7 @@ export default {
|
||||
home: "Trang chủ",
|
||||
inbounds: "Đầu Vào",
|
||||
outbounds: "Đầu ra",
|
||||
endpoints: "Câu hình",
|
||||
clients: "Khách hàng",
|
||||
rules: "Quy tắc",
|
||||
tls: "Cài đặt TLS",
|
||||
@@ -75,6 +76,8 @@ export default {
|
||||
inbound: "Đầu Vào",
|
||||
client: "Máy Khách hàng",
|
||||
outbound: "Đầu Ra",
|
||||
endpoint: "Điểm cuối",
|
||||
config: "Câu hình",
|
||||
rule: "Quy tắc",
|
||||
user: "Người dùng",
|
||||
tag: "Thẻ",
|
||||
@@ -228,7 +231,6 @@ export default {
|
||||
worker: "Công nhân",
|
||||
ifName: "Tên Giao diện",
|
||||
sysIf: "Giao diện Hệ thống",
|
||||
gso: "Giao Thức GSO",
|
||||
options: "Tùy chọn Wireguard",
|
||||
multiPeer: "Nhiều Đối tác",
|
||||
allowedIp: "IPs được Phép",
|
||||
@@ -395,7 +397,6 @@ export default {
|
||||
download: "Tải xuống",
|
||||
volume: "Thể tích",
|
||||
usage: "Sử dụng",
|
||||
enable: "Kích hoạt thống kê",
|
||||
graphTitle: "Biểu đồ lưu lượng",
|
||||
B: "B",
|
||||
KB: "KB",
|
||||
|
||||
@@ -37,6 +37,7 @@ export default {
|
||||
home: "主页",
|
||||
inbounds: "入站管理",
|
||||
outbounds: "出站管理",
|
||||
endpoints: "节点管理",
|
||||
clients: "用户管理",
|
||||
rules: "路由列表",
|
||||
tls: "TLS 设置",
|
||||
@@ -75,6 +76,8 @@ export default {
|
||||
inbound: "入站",
|
||||
client: "客户端",
|
||||
outbound: "出站",
|
||||
endpoint: "节点",
|
||||
config: "配置",
|
||||
rule: "规则",
|
||||
user: "用户",
|
||||
tag: "标签",
|
||||
@@ -228,7 +231,6 @@ export default {
|
||||
worker: "工作线程",
|
||||
ifName: "接口名称",
|
||||
sysIf: "系统接口",
|
||||
gso: "分段卸载",
|
||||
options: "WireGuard 选项",
|
||||
multiPeer: "多对等体",
|
||||
allowedIp: "允许的 IP 地址",
|
||||
@@ -395,7 +397,6 @@ export default {
|
||||
download: "下载",
|
||||
volume: "流量",
|
||||
usage: "已用",
|
||||
enable: "启用统计",
|
||||
graphTitle: "流量图表",
|
||||
B: "B",
|
||||
KB: "KB",
|
||||
|
||||
@@ -38,6 +38,7 @@ export default {
|
||||
home: "主頁",
|
||||
inbounds: "入站管理",
|
||||
outbounds: "出站管理",
|
||||
endpoints: "端點管理",
|
||||
clients: "用戶管理",
|
||||
rules: "路由列表",
|
||||
tls: "TLS 設置",
|
||||
@@ -76,6 +77,8 @@ export default {
|
||||
inbound: "入站",
|
||||
client: "客戶端",
|
||||
outbound: "出站",
|
||||
endpoint: "端點",
|
||||
config: "配置",
|
||||
rule: "規則",
|
||||
user: "用戶",
|
||||
tag: "標簽",
|
||||
@@ -229,7 +232,6 @@ export default {
|
||||
worker: "工作線程",
|
||||
ifName: "介面名稱",
|
||||
sysIf: "系統介面",
|
||||
gso: "分段卸載",
|
||||
options: "Wireguard 選項",
|
||||
multiPeer: "多對等方",
|
||||
allowedIp: "允許的 IP",
|
||||
@@ -396,7 +398,6 @@ export default {
|
||||
download: "下載",
|
||||
volume: "流量",
|
||||
usage: "已用",
|
||||
enable: "啟用統計",
|
||||
graphTitle: "流量圖表",
|
||||
B: "B",
|
||||
KB: "KB",
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export interface Addr {
|
||||
server: string
|
||||
server_port: number
|
||||
tls?: boolean
|
||||
insecure?: boolean
|
||||
server_name?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export interface InData {
|
||||
id: number
|
||||
tag: string
|
||||
addrs: Addr[]
|
||||
outJson: any
|
||||
}
|
||||
@@ -15,24 +15,24 @@ function utf8ToBase64(utf8String: string): string {
|
||||
}
|
||||
|
||||
export namespace LinkUtil {
|
||||
export function linkGenerator(user: Client, inbound: Inbound, tlsClient: any = {}, addrs: any[] = []): string[] {
|
||||
export function linkGenerator(user: Client, inbound: Inbound, tls: any = {}, addrs: any[] = []): string[] {
|
||||
switch(inbound.type){
|
||||
case InTypes.Shadowsocks:
|
||||
return shadowsocksLink(user,<Shadowsocks>inbound, addrs)
|
||||
case InTypes.Naive:
|
||||
return naiveLink(user,<Naive>inbound, addrs, tlsClient)
|
||||
return naiveLink(user,<Naive>inbound, addrs, tls)
|
||||
case InTypes.Hysteria:
|
||||
return hysteriaLink(user,<Hysteria>inbound, addrs, tlsClient)
|
||||
return hysteriaLink(user,<Hysteria>inbound, addrs, tls)
|
||||
case InTypes.Hysteria2:
|
||||
return hysteria2Link(user,<Hysteria2>inbound, addrs, tlsClient)
|
||||
return hysteria2Link(user,<Hysteria2>inbound, addrs, tls)
|
||||
case InTypes.TUIC:
|
||||
return tuicLink(user,<TUIC>inbound, addrs, tlsClient)
|
||||
return tuicLink(user,<TUIC>inbound, addrs, tls)
|
||||
case InTypes.VLESS:
|
||||
return vlessLink(user,<VLESS>inbound, addrs, tlsClient)
|
||||
return vlessLink(user,<VLESS>inbound, addrs, tls)
|
||||
case InTypes.Trojan:
|
||||
return trojanLink(user,<Trojan>inbound, addrs, tlsClient)
|
||||
return trojanLink(user,<Trojan>inbound, addrs, tls)
|
||||
case InTypes.VMess:
|
||||
return vmessLink(user,<VMess>inbound, addrs, tlsClient)
|
||||
return vmessLink(user,<VMess>inbound, addrs, tls)
|
||||
}
|
||||
return []
|
||||
}
|
||||
@@ -71,17 +71,17 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function hysteriaLink(user: Client, inbound: Hysteria, addrs: any[], tlsClient: any): string[] {
|
||||
function hysteriaLink(user: Client, inbound: Hysteria, addrs: any[], tls: any): string[] {
|
||||
const auth = user.config.hysteria.auth_str
|
||||
const params = {
|
||||
upmbps: inbound.up_mbps?? null,
|
||||
downmbps: inbound.down_mbps?? null,
|
||||
auth: auth?? null,
|
||||
peer: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
peer: tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
obfsParam: inbound.obfs?? null,
|
||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||
insecure: tlsClient?.insecure ? 1 : null
|
||||
insecure: tls?.client?.insecure ? 1 : null
|
||||
}
|
||||
|
||||
let links = <string[]>[]
|
||||
@@ -105,12 +105,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('peer', a.server_name)
|
||||
} else {
|
||||
inbound.tls.server_name ? uri.searchParams.set('peer', inbound.tls.server_name) : uri.searchParams.delete('peer')
|
||||
tls?.server?.server_name ? uri.searchParams.set('peer', tls?.server?.server_name) : uri.searchParams.delete('peer')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('insecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -119,17 +119,17 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function hysteria2Link(user: Client, inbound: Hysteria2, addrs: any[], tlsClient: any): string[] {
|
||||
function hysteria2Link(user: Client, inbound: Hysteria2, addrs: any[], tls: any): string[] {
|
||||
const password = user.config.hysteria2.password
|
||||
const params = {
|
||||
upmbps: inbound.up_mbps?? null,
|
||||
downmbps: inbound.down_mbps?? null,
|
||||
sni: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
sni: tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
obfs: inbound.obfs?.type?? null,
|
||||
'obfs-password': inbound.obfs?.password?? null,
|
||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||
insecure: tlsClient?.insecure ? 1 : null
|
||||
insecure: tls?.client?.insecure ? 1 : null
|
||||
}
|
||||
|
||||
let links = <string[]>[]
|
||||
@@ -153,12 +153,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('sni', a.server_name)
|
||||
} else {
|
||||
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||
tls?.server?.server_name ? uri.searchParams.set('sni', tls?.server?.server_name) : uri.searchParams.delete('sni')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('insecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -167,17 +167,17 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function naiveLink(user: Client, inbound: Naive, addrs: any[], tlsClient: any): string[] {
|
||||
function naiveLink(user: Client, inbound: Naive, addrs: any[], tls: any): string[] {
|
||||
const password = user.config.naive.password
|
||||
|
||||
let links = <string[]>[]
|
||||
if (addrs.length == 0) {
|
||||
const params = {
|
||||
padding: 1,
|
||||
peer: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
peer: tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null
|
||||
allowInsecure: tls?.client?.insecure ? 1 : null
|
||||
}
|
||||
const uri = `http2://${utf8ToBase64(user.name + ":" + password + "@" + location.hostname + ":" + inbound.listen_port)}`
|
||||
const paramsArray = []
|
||||
@@ -191,10 +191,10 @@ export namespace LinkUtil {
|
||||
addrs.forEach(a => {
|
||||
const params = {
|
||||
padding: 1,
|
||||
peer: a.server_name?.length>0 ? a.server_name : inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
peer: a.server_name?.length>0 ? a.server_name : tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||
allowInsecure: a.insecure ? 1 : tlsClient?.insecure ? 1 : null
|
||||
allowInsecure: a.insecure ? 1 : tls?.client?.insecure ? 1 : null
|
||||
}
|
||||
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + a.server + ":" + a.server_port)}`
|
||||
const paramsArray = []
|
||||
@@ -209,14 +209,14 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function tuicLink(user: Client, inbound: TUIC, addrs: any[], tlsClient: any): string[] {
|
||||
function tuicLink(user: Client, inbound: TUIC, addrs: any[], tls: any): string[] {
|
||||
const u = user.config.tuic
|
||||
const params = {
|
||||
sni: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
sni: tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
congestion_control: inbound.congestion_control?? null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
disable_sni: tlsClient?.disable_sni ? 1 : null
|
||||
allowInsecure: tls?.client?.insecure ? 1 : null,
|
||||
disable_sni: tls?.client?.disable_sni ? 1 : null
|
||||
}
|
||||
|
||||
let links = <string[]>[]
|
||||
@@ -240,12 +240,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('sni', a.server_name)
|
||||
} else {
|
||||
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||
tls?.server?.server_name ? uri.searchParams.set('sni', tls?.server?.server_name) : uri.searchParams.delete('sni')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('allowInsecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -287,7 +287,7 @@ export namespace LinkUtil {
|
||||
return params
|
||||
}
|
||||
|
||||
function vlessLink(user: Client, inbound: VLESS, addrs: any[], tlsClient: any): string[] {
|
||||
function vlessLink(user: Client, inbound: VLESS, addrs: any[], tls: any): string[] {
|
||||
const u = user.config.vless
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -295,14 +295,14 @@ export namespace LinkUtil {
|
||||
|
||||
const params = {
|
||||
type: transport?.type?? 'tcp',
|
||||
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||
sni: inbound.tls?.server_name?? null,
|
||||
flow: inbound.tls?.enabled ? u?.flow?? null : null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
||||
pbk: tlsClient?.reality?.public_key?? null,
|
||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
||||
security: tls?.server?.enabled? tls?.server?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
sni: tls?.server?.server_name?? null,
|
||||
flow: tls?.server?.enabled ? u?.flow?? null : null,
|
||||
allowInsecure: tls?.client?.insecure ? 1 : null,
|
||||
fp: tls?.client?.utls?.enabled ? tls.client.utls.fingerprint : null,
|
||||
pbk: tls?.client?.reality?.public_key?? null,
|
||||
sid: tls?.server?.reality?.enabled ? (tls?.server?.reality?.short_id?.length>0 ? tls.server.reality.short_id[RandomUtil.randomInt(tls.server.reality.short_id.length)] : null) : null
|
||||
}
|
||||
let links = <string[]>[]
|
||||
if (addrs.length == 0) {
|
||||
@@ -335,12 +335,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('sni', a.server_name)
|
||||
} else {
|
||||
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||
tls?.server?.server_name ? uri.searchParams.set('sni', tls?.server?.server_name) : uri.searchParams.delete('sni')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('allowInsecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -349,7 +349,7 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function trojanLink(user: Client, inbound: Trojan, addrs: any[], tlsClient: any): string[] {
|
||||
function trojanLink(user: Client, inbound: Trojan, addrs: any[], tls: any): string[] {
|
||||
const u = user.config.trojan
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -357,13 +357,13 @@ export namespace LinkUtil {
|
||||
|
||||
const params = {
|
||||
type: transport?.type?? 'tcp',
|
||||
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||
sni: inbound.tls?.server_name?? null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
||||
pbk: tlsClient?.reality?.public_key?? null,
|
||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
||||
security: tls?.server?.enabled? tls?.server?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
sni: tls?.server?.server_name?? null,
|
||||
allowInsecure: tls?.client?.insecure ? 1 : null,
|
||||
fp: tls?.client?.utls?.enabled ? tls.client.utls.fingerprint : null,
|
||||
pbk: tls?.client?.reality?.public_key?? null,
|
||||
sid: tls?.server?.reality?.enabled ? (tls?.server?.reality?.short_id?.length>0 ? tls?.server?.reality.short_id[RandomUtil.randomInt(tls?.server?.reality.short_id.length)] : null) : null
|
||||
}
|
||||
|
||||
let links = <string[]>[]
|
||||
@@ -397,12 +397,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('sni', a.server_name)
|
||||
} else {
|
||||
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||
tls?.server?.server_name ? uri.searchParams.set('sni', tls?.server?.server_name) : uri.searchParams.delete('sni')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('allowInsecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -411,7 +411,7 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function vmessLink(user: Client, inbound: VMess, addrs: any[], tlsClient: any): string[] {
|
||||
function vmessLink(user: Client, inbound: VMess, addrs: any[], tls: any): string[] {
|
||||
const u = user.config.vmess
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -429,9 +429,9 @@ export namespace LinkUtil {
|
||||
path: tParams.path?? undefined,
|
||||
port: inbound.listen_port,
|
||||
ps: inbound.tag,
|
||||
sni: inbound.tls.server_name?? undefined,
|
||||
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none',
|
||||
allowInsecure: tlsClient?.insecure ? 1 : undefined
|
||||
sni: tls?.server?.server_name?? undefined,
|
||||
tls: tls?.server && Object.keys(tls.server).length>0? 'tls' : 'none',
|
||||
allowInsecure: tls?.client?.insecure ? 1 : undefined
|
||||
}
|
||||
let links = <string[]>[]
|
||||
if (addrs.length == 0) {
|
||||
|
||||
@@ -3,50 +3,49 @@ import { iTls } from "@/types/inTls"
|
||||
import { oTls } from "@/types/outTls"
|
||||
import RandomUtil from "./randomUtil"
|
||||
|
||||
export function fillData(out: any, inbound: Inbound, tlsClient: any) {
|
||||
if (Object.hasOwn(inbound, 'tls')) {
|
||||
const inb = <any>inbound
|
||||
addTls(out,inb.tls,tlsClient)
|
||||
export function fillData(inbound: Inbound, tls: any | null = null) {
|
||||
if (tls != null) {
|
||||
addTls(inbound.out_json, tls.server, tls.client)
|
||||
} else {
|
||||
delete out.tls
|
||||
delete inbound.out_json.tls
|
||||
}
|
||||
out.type = inbound.type
|
||||
out.tag = inbound.tag
|
||||
out.server = location.hostname
|
||||
out.server_port = inbound.listen_port
|
||||
inbound.out_json.type = inbound.type
|
||||
inbound.out_json.tag = inbound.tag
|
||||
inbound.out_json.server = location.hostname
|
||||
inbound.out_json.server_port = inbound.listen_port
|
||||
switch(inbound.type){
|
||||
case InTypes.HTTP: case InTypes.SOCKS: case InTypes.Mixed:
|
||||
return
|
||||
case InTypes.Shadowsocks:
|
||||
shadowsocksOut(out, <Shadowsocks>inbound)
|
||||
shadowsocksOut(inbound.out_json, <Shadowsocks>inbound)
|
||||
return
|
||||
case InTypes.ShadowTLS:
|
||||
shadowTlsOut(out, <ShadowTLS>inbound)
|
||||
shadowTlsOut(inbound.out_json, <ShadowTLS>inbound)
|
||||
return
|
||||
case InTypes.Hysteria:
|
||||
hysteriaOut(out, <Hysteria>inbound)
|
||||
hysteriaOut(inbound.out_json, <Hysteria>inbound)
|
||||
return
|
||||
case InTypes.Hysteria2:
|
||||
hysteria2Out(out, <Hysteria2>inbound)
|
||||
hysteria2Out(inbound.out_json, <Hysteria2>inbound)
|
||||
return
|
||||
case InTypes.TUIC:
|
||||
tuicOut(out, <TUIC>inbound)
|
||||
tuicOut(inbound.out_json, <TUIC>inbound)
|
||||
return
|
||||
case InTypes.VLESS:
|
||||
vlessOut(out, <VLESS>inbound)
|
||||
vlessOut(inbound.out_json, <VLESS>inbound)
|
||||
return
|
||||
case InTypes.Trojan:
|
||||
trojanOut(out, <Trojan>inbound)
|
||||
trojanOut(inbound.out_json, <Trojan>inbound)
|
||||
return
|
||||
case InTypes.VMess:
|
||||
vmessOut(out, <VMess>inbound)
|
||||
vmessOut(inbound.out_json, <VMess>inbound)
|
||||
return
|
||||
}
|
||||
Object.keys(out).forEach(key => delete out[key])
|
||||
Object.keys(inbound.out_json).forEach(key => delete inbound.out_json[key])
|
||||
}
|
||||
|
||||
function addTls(out: any, tls: iTls, tlsClient: oTls){
|
||||
out.tls = tlsClient
|
||||
out.tls = tlsClient?? {}
|
||||
if(tls.enabled) out.tls.enabled = tls.enabled
|
||||
if(tls.server_name) out.tls.server_name = tls.server_name
|
||||
if(tls.alpn) out.tls.alpn = tls.alpn
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
const WgUtil = {
|
||||
gf(init?: Float64Array): Float64Array {
|
||||
var r = new Float64Array(16)
|
||||
if (init) {
|
||||
for (var i = 0; i < init.length; ++i)
|
||||
r[i] = init[i]
|
||||
}
|
||||
return r
|
||||
},
|
||||
|
||||
pack(o: Float64Array, n: Float64Array): Float64Array {
|
||||
var b, m = this.gf(), t = this.gf()
|
||||
for (var i = 0; i < 16; ++i)
|
||||
t[i] = n[i]
|
||||
this.carry(t)
|
||||
this.carry(t)
|
||||
this.carry(t)
|
||||
for (var j = 0; j < 2; ++j) {
|
||||
m[0] = t[0] - 0xffed
|
||||
for (var i = 1; i < 15; ++i) {
|
||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1)
|
||||
m[i - 1] &= 0xffff
|
||||
}
|
||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1)
|
||||
b = (m[15] >> 16) & 1
|
||||
m[14] &= 0xffff
|
||||
this.cswap(t, m, 1 - b)
|
||||
}
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[2 * i] = t[i] & 0xff
|
||||
o[2 * i + 1] = t[i] >> 8
|
||||
}
|
||||
},
|
||||
|
||||
carry(o: Float64Array) {
|
||||
var c
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536)
|
||||
o[i] &= 0xffff
|
||||
}
|
||||
},
|
||||
|
||||
cswap(p: Float64Array, q: Float64Array, b: number) {
|
||||
var t, c = ~(b - 1)
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
t = c & (p[i] ^ q[i])
|
||||
p[i] ^= t
|
||||
q[i] ^= t
|
||||
}
|
||||
},
|
||||
|
||||
add(o: Float64Array, a: Float64Array, b: Float64Array) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] + b[i]) | 0
|
||||
},
|
||||
|
||||
subtract(o: Float64Array, a: Float64Array, b: Float64Array) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] - b[i]) | 0
|
||||
},
|
||||
|
||||
multmod(o: Float64Array, a: Float64Array, b: Float64Array) {
|
||||
var t = new Float64Array(31)
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
for (var j = 0; j < 16; ++j)
|
||||
t[i + j] += a[i] * b[j]
|
||||
}
|
||||
for (var i = 0; i < 15; ++i)
|
||||
t[i] += 38 * t[i + 16]
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = t[i]
|
||||
this.carry(o)
|
||||
this.carry(o)
|
||||
},
|
||||
|
||||
invert(o: Float64Array, i: Float64Array) {
|
||||
var c = this.gf()
|
||||
for (var a = 0; a < 16; ++a)
|
||||
c[a] = i[a]
|
||||
for (var a = 253; a >= 0; --a) {
|
||||
this.multmod(c, c, c)
|
||||
if (a !== 2 && a !== 4)
|
||||
this.multmod(c, c, i)
|
||||
}
|
||||
for (var a = 0; a < 16; ++a)
|
||||
o[a] = c[a]
|
||||
},
|
||||
|
||||
clamp(z) {
|
||||
z[31] = (z[31] & 127) | 64
|
||||
z[0] &= 248
|
||||
},
|
||||
|
||||
generatePublicKey(privateKey: Uint8Array): Uint8Array {
|
||||
var r, z = new Uint8Array(32)
|
||||
var a = this.gf([1]),
|
||||
b = this.gf([9]),
|
||||
c = this.gf(),
|
||||
d = this.gf([1]),
|
||||
e = this.gf(),
|
||||
f = this.gf(),
|
||||
_121665 = this.gf([0xdb41, 1]),
|
||||
_9 = this.gf([9])
|
||||
for (var i = 0; i < 32; ++i)
|
||||
z[i] = privateKey[i]
|
||||
this.clamp(z)
|
||||
for (var i = 254; i >= 0; --i) {
|
||||
r = (z[i >>> 3] >>> (i & 7)) & 1
|
||||
this.cswap(a, b, r)
|
||||
this.cswap(c, d, r)
|
||||
this.add(e, a, c)
|
||||
this.subtract(a, a, c)
|
||||
this.add(c, b, d)
|
||||
this.subtract(b, b, d)
|
||||
this.multmod(d, e, e)
|
||||
this.multmod(f, a, a)
|
||||
this.multmod(a, c, a)
|
||||
this.multmod(c, b, e)
|
||||
this.add(e, a, c)
|
||||
this.subtract(a, a, c)
|
||||
this.multmod(b, a, a)
|
||||
this.subtract(c, d, f)
|
||||
this.multmod(a, c, _121665)
|
||||
this.add(a, a, d)
|
||||
this.multmod(c, c, a)
|
||||
this.multmod(a, d, f)
|
||||
this.multmod(d, b, _9)
|
||||
this.multmod(b, e, e)
|
||||
this.cswap(a, b, r)
|
||||
this.cswap(c, d, r)
|
||||
}
|
||||
this.invert(c, c)
|
||||
this.multmod(a, a, c)
|
||||
this.pack(z, a)
|
||||
return z
|
||||
},
|
||||
|
||||
generatePresharedKey(): Uint8Array {
|
||||
var privateKey = new Uint8Array(32)
|
||||
window.crypto.getRandomValues(privateKey)
|
||||
return privateKey
|
||||
},
|
||||
|
||||
generatePrivateKey(): Uint8Array {
|
||||
var privateKey = this.generatePresharedKey()
|
||||
this.clamp(privateKey)
|
||||
return privateKey
|
||||
},
|
||||
|
||||
encodeBase64(dest: Uint8Array, src: Uint8Array) {
|
||||
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63])
|
||||
for (var i = 0; i < 4; ++i)
|
||||
dest[i] = input[i] + 65 +
|
||||
(((25 - input[i]) >> 8) & 6) -
|
||||
(((51 - input[i]) >> 8) & 75) -
|
||||
(((61 - input[i]) >> 8) & 15) +
|
||||
(((62 - input[i]) >> 8) & 3)
|
||||
},
|
||||
|
||||
keyToBase64(key: Uint8Array): string {
|
||||
var i, base64 = new Uint8Array(44)
|
||||
for (i = 0; i < 32 / 3; ++i)
|
||||
this.encodeBase64(base64.subarray(i * 4), key.subarray(i * 3))
|
||||
this.encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]))
|
||||
base64[43] = 61
|
||||
return String.fromCharCode.apply(null, base64)
|
||||
},
|
||||
|
||||
keyFromBase64(encoded: string): Uint8Array {
|
||||
const binaryStr = atob(encoded)
|
||||
const bytes = new Uint8Array(binaryStr.length)
|
||||
for (let i = 0; i < binaryStr.length; i++) {
|
||||
bytes[i] = binaryStr.charCodeAt(i)
|
||||
}
|
||||
return bytes
|
||||
},
|
||||
|
||||
generateKeypair(secretKey?: string ='') {
|
||||
var privateKey = secretKey.length>0 ? this.keyFromBase64(secretKey) : this.generatePrivateKey()
|
||||
var publicKey = this.generatePublicKey(privateKey)
|
||||
return {
|
||||
publicKey: this.keyToBase64(publicKey),
|
||||
privateKey: secretKey.length>0 ? secretKey : this.keyToBase64(privateKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default WgUtil
|
||||
@@ -34,6 +34,11 @@ const routes = [
|
||||
name: 'pages.outbounds',
|
||||
component: () => import('@/views/Outbounds.vue'),
|
||||
},
|
||||
{
|
||||
path: '/endpoints',
|
||||
name: 'pages.endpoints',
|
||||
component: () => import('@/views/Endpoints.vue'),
|
||||
},
|
||||
{
|
||||
path: '/rules',
|
||||
name: 'pages.rules',
|
||||
|
||||
@@ -3,6 +3,9 @@ import HttpUtils from '@/plugins/httputil'
|
||||
import { defineStore } from 'pinia'
|
||||
import { push } from 'notivue'
|
||||
import { i18n } from '@/locales'
|
||||
import { Inbound } from '@/types/inbounds'
|
||||
import { Outbound } from '@/types/outbounds'
|
||||
import { Endpoint } from '@/types/endpoints'
|
||||
|
||||
const Data = defineStore('Data', {
|
||||
state: () => ({
|
||||
@@ -10,23 +13,17 @@ const Data = defineStore('Data', {
|
||||
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
||||
subURI: "",
|
||||
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
||||
oldData: <{config: any, clients: any[], tlsConfigs: any[], inData: any[]}>{},
|
||||
config: <any>{},
|
||||
inbounds: <Inbound[]>[],
|
||||
outbounds: <Outbound[]>[],
|
||||
endpoints: <Endpoint[]>[],
|
||||
clients: [],
|
||||
tlsConfigs: [],
|
||||
inData: [],
|
||||
tlsConfigs: <any[]>[],
|
||||
}),
|
||||
actions: {
|
||||
async loadData() {
|
||||
const msg = await HttpUtils.get('api/load', this.lastLoad >0 ? {lu: this.lastLoad} : {} )
|
||||
if(msg.success) {
|
||||
this.lastLoad = Math.floor((new Date()).getTime()/1000)
|
||||
|
||||
// Set new data
|
||||
if (msg.obj.config) this.oldData.config = msg.obj.config
|
||||
if (msg.obj.clients) this.oldData.clients = msg.obj.clients
|
||||
if (msg.obj.tls) this.oldData.tlsConfigs = msg.obj.tls
|
||||
if (msg.obj.inData) this.oldData.inData = msg.obj.inData
|
||||
this.onlines = msg.obj.onlines
|
||||
if (msg.obj.lastLog) {
|
||||
push.error({
|
||||
@@ -37,84 +34,50 @@ const Data = defineStore('Data', {
|
||||
}
|
||||
|
||||
if (msg.obj.config) {
|
||||
// To avoid ref copy
|
||||
const data = JSON.parse(JSON.stringify(msg.obj))
|
||||
if (data.subURI) this.subURI = data.subURI
|
||||
if (data.config) this.config = data.config
|
||||
if (data.clients) this.clients = data.clients
|
||||
if (data.tls) this.tlsConfigs = data.tls
|
||||
if (data.inData) this.inData = data.inData
|
||||
this.setNewData(msg.obj)
|
||||
}
|
||||
}
|
||||
},
|
||||
async pushData() {
|
||||
const diff = {
|
||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config), null, 2),
|
||||
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
||||
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
||||
inData: JSON.stringify(FindDiff.ArrObj(this.inData,this.oldData.inData, "inData"), null, 2),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.lastLoad = 0
|
||||
this.loadData()
|
||||
}
|
||||
setNewData(data: any) {
|
||||
this.lastLoad = Math.floor((new Date()).getTime()/1000)
|
||||
if (data.subURI) this.subURI = data.subURI
|
||||
if (data.config) this.config = data.config
|
||||
if (data.clients) this.clients = data.clients
|
||||
if (data.inbounds) this.inbounds = data.inbounds
|
||||
if (data.outbounds) this.outbounds = data.outbounds
|
||||
if (data.endpoints) this.endpoints = data.endpoints
|
||||
if (data.tls) this.tlsConfigs = data.tls
|
||||
},
|
||||
async delInbound(index: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
|
||||
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
||||
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
||||
inData: <string|undefined> undefined,
|
||||
}
|
||||
|
||||
// Validate inData
|
||||
let invalidInData = <any[]>[]
|
||||
this.inData.forEach((d:any) => {
|
||||
const inboundIndex = this.config.inbounds.findIndex((i:any) => i.tag == d.tag)
|
||||
if (inboundIndex == -1) invalidInData.push({key: "inData", action: "del", index: d.id, obj: null})
|
||||
})
|
||||
if (invalidInData.length>0) {
|
||||
diff.inData = JSON.stringify(invalidInData)
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
async loadInbounds(ids: number[]): Promise<Inbound[]> {
|
||||
const options = ids.length > 0 ? {id: ids.join(",")} : {}
|
||||
const msg = await HttpUtils.get('api/inbounds', options)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
return msg.obj.inbounds
|
||||
}
|
||||
return <Inbound[]>[]
|
||||
},
|
||||
async delInData(id: number) {
|
||||
const diff = {
|
||||
inData: JSON.stringify([{key: "inData", action: "del", index: id, obj: null}])
|
||||
async save (object: string, action: string, data: any, userLinks: any[] | null = null, outJsons: any[] | null = null): Promise<boolean> {
|
||||
let postData = {
|
||||
object: object,
|
||||
action: action,
|
||||
data: JSON.stringify(data),
|
||||
userLinks: userLinks == null ? undefined : JSON.stringify(userLinks),
|
||||
outJsons: outJsons == null ? undefined : JSON.stringify(outJsons),
|
||||
}
|
||||
await HttpUtils.post('api/save',diff)
|
||||
},
|
||||
async delOutbound(index: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
||||
if (userLinks == null) {
|
||||
delete postData.userLinks
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
async delClient(id: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
|
||||
clients:JSON.stringify([{key: "clients", action: "del", index: id, obj: null}]),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
async delTls(id: number) {
|
||||
const diff = {
|
||||
tls:JSON.stringify([{key: "tls", action: "del", index: id, obj: null}]),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
const msg = await HttpUtils.post('api/save', postData)
|
||||
if (msg.success) {
|
||||
const objectName = ['tls', 'config'].includes(object) ? object : object.substring(0, object.length - 1)
|
||||
push.success({
|
||||
title: i18n.global.t('success'),
|
||||
duration: 5000,
|
||||
message: i18n.global.t('actions.' + action) + " " + i18n.global.t('objects.' + objectName)
|
||||
})
|
||||
this.setNewData(msg.obj)
|
||||
}
|
||||
return msg.success
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface Client {
|
||||
enable: boolean
|
||||
name: string
|
||||
config: Config
|
||||
inbounds: string[]
|
||||
inbounds: number[]
|
||||
links: Link[]
|
||||
volume: number
|
||||
expiry: number
|
||||
|
||||
@@ -83,8 +83,6 @@ interface RouteRuleSet {
|
||||
|
||||
interface Experimental {
|
||||
cache_file?: CacheFile
|
||||
clash_api?: ClashApi
|
||||
v2ray_api: V2rayApi
|
||||
}
|
||||
|
||||
interface CacheFile {
|
||||
@@ -94,27 +92,6 @@ interface CacheFile {
|
||||
store_fakeip?: boolean
|
||||
}
|
||||
|
||||
interface V2rayApi {
|
||||
listen: string
|
||||
stats: V2rayApiStats
|
||||
}
|
||||
|
||||
export interface V2rayApiStats {
|
||||
enabled: boolean
|
||||
inbounds: string[]
|
||||
outbounds: string[]
|
||||
users: string[]
|
||||
}
|
||||
|
||||
interface ClashApi {
|
||||
external_controller?: string
|
||||
external_ui?: string
|
||||
external_ui_download_url?: string
|
||||
external_ui_download_detour?: string
|
||||
secret?: string
|
||||
default_mode?: string
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
log: Log
|
||||
dns: Dns
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Dial } from "./outbounds"
|
||||
|
||||
export const EpTypes = {
|
||||
Wireguard: 'wireguard',
|
||||
}
|
||||
|
||||
type EpType = typeof EpTypes[keyof typeof EpTypes]
|
||||
|
||||
|
||||
|
||||
interface EndpointBasics {
|
||||
id: number
|
||||
type: EpType
|
||||
tag: string
|
||||
}
|
||||
|
||||
export interface WgPeer {
|
||||
address: string
|
||||
port: number
|
||||
public_key: string
|
||||
pre_shared_key?: string
|
||||
allowed_ips?: string[]
|
||||
persistent_keepalive_interval?: number
|
||||
reserved?: number[]
|
||||
}
|
||||
|
||||
export interface WireGuard extends EndpointBasics, Dial {
|
||||
system?: boolean
|
||||
name?: string
|
||||
mtu?: number
|
||||
address: string[]
|
||||
private_key: string
|
||||
listen_port: number,
|
||||
peers: WgPeer[]
|
||||
udp_timeout?: string,
|
||||
workers?: number
|
||||
}
|
||||
|
||||
// Create interfaces dynamically based on EpTypes keys
|
||||
type InterfaceMap = {
|
||||
[Key in keyof typeof EpTypes]: {
|
||||
type: string
|
||||
[otherProperties: string]: any; // You can add other properties as needed
|
||||
}
|
||||
}
|
||||
|
||||
// Create union type from InterfaceMap
|
||||
export type Endpoint = InterfaceMap[keyof InterfaceMap]
|
||||
|
||||
// Create defaultValues object dynamically
|
||||
const defaultValues: Record<EpType, Endpoint> = {
|
||||
wireguard: { type: EpTypes.Wireguard, address: ['10.0.0.2/32','fe80::2/128'], private_key: '', listen_port: 0, peers: [{ address: '', port: 0, public_key: ''}] },
|
||||
}
|
||||
|
||||
export function createEndpoint<T extends Endpoint>(type: string,json?: Partial<T>): Endpoint {
|
||||
const defaultObject: Endpoint = { ...defaultValues[type], ...(json || {}) }
|
||||
return defaultObject
|
||||
}
|
||||
@@ -24,6 +24,15 @@ export const InTypes = {
|
||||
|
||||
type InType = typeof InTypes[keyof typeof InTypes]
|
||||
|
||||
export interface Addr {
|
||||
server: string
|
||||
server_port: number
|
||||
tls?: boolean
|
||||
insecure?: boolean
|
||||
server_name?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export interface Listen {
|
||||
listen: string
|
||||
listen_port: number
|
||||
@@ -39,8 +48,12 @@ export interface Listen {
|
||||
}
|
||||
|
||||
interface InboundBasics extends Listen {
|
||||
id: number
|
||||
type: InType
|
||||
tag: string
|
||||
tls_id: number
|
||||
addrs?: Addr[]
|
||||
out_json?: any
|
||||
}
|
||||
|
||||
interface UsernamePass {
|
||||
@@ -87,7 +100,6 @@ export interface SOCKS extends InboundBasics {
|
||||
}
|
||||
export interface HTTP extends InboundBasics {
|
||||
users?: UsernamePass[]
|
||||
tls?: iTls,
|
||||
}
|
||||
export interface Shadowsocks extends InboundBasics {
|
||||
method: string
|
||||
@@ -125,7 +137,6 @@ export interface Hysteria extends InboundBasics {
|
||||
recv_window_client?: number
|
||||
max_conn_client?: number
|
||||
disable_mtu_discovery?: boolean
|
||||
tls: iTls
|
||||
}
|
||||
export interface ShadowTLS extends InboundBasics {
|
||||
version: 1|2|3
|
||||
@@ -139,7 +150,6 @@ export interface ShadowTLS extends InboundBasics {
|
||||
}
|
||||
export interface VLESS extends InboundBasics {
|
||||
users: VlessUser[]
|
||||
tls?: iTls
|
||||
multiplex?: iMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
@@ -149,7 +159,6 @@ export interface TUIC extends InboundBasics {
|
||||
auth_timeout?: string
|
||||
zero_rtt_handshake?: boolean
|
||||
heartbeat?: string
|
||||
tls: iTls
|
||||
}
|
||||
export interface Hysteria2 extends InboundBasics {
|
||||
up_mbps?: number
|
||||
@@ -160,7 +169,6 @@ export interface Hysteria2 extends InboundBasics {
|
||||
}
|
||||
users: NamePass[]
|
||||
ignore_client_bandwidth?: boolean
|
||||
tls: iTls
|
||||
masquerade?: string
|
||||
brutal_debug?: boolean
|
||||
}
|
||||
@@ -234,24 +242,26 @@ type userEnabledTypes = {
|
||||
vless: VLESS
|
||||
}
|
||||
|
||||
export const inboundWithUsers = ['mixed', 'socks:', 'http', 'shadowsocks', 'vmess', 'trojan', 'naive', 'hysteria', 'shadowtls', 'tuic', 'hysteria2', 'vless']
|
||||
|
||||
// Create union type from userEnabledTypes
|
||||
export type InboundWithUser = userEnabledTypes[keyof userEnabledTypes]
|
||||
type InboundWithUser = userEnabledTypes[keyof userEnabledTypes]
|
||||
|
||||
// Create defaultValues object dynamically
|
||||
const defaultValues: Record<InType, Inbound> = {
|
||||
direct: <Direct>{ type: InTypes.Direct },
|
||||
mixed: <Mixed>{ type: InTypes.Mixed },
|
||||
socks: <SOCKS>{ type: InTypes.SOCKS },
|
||||
http: <HTTP>{ type: InTypes.HTTP, tls: {} },
|
||||
http: <HTTP>{ type: InTypes.HTTP, tls_id: 0 },
|
||||
shadowsocks: <Shadowsocks>{ type: InTypes.Shadowsocks, method: 'none', multiplex: {} },
|
||||
vmess: <VMess>{ type: InTypes.VMess, users: <VmessUser[]>[], tls: {}, multiplex: {}, transport: {} },
|
||||
trojan: <Trojan>{ type: InTypes.Trojan, users: <NamePass[]>[], tls: {}, multiplex: {}, transport: {} },
|
||||
naive: <Naive>{ type: InTypes.Naive, users: <UsernamePass[]>[], tls: { enabled: true } },
|
||||
hysteria: <Hysteria>{ type: InTypes.Hysteria, users: <NameAuth[]>[], up_mbps: 100, down_mbps: 100, tls: { enabled: true } },
|
||||
vmess: <VMess>{ type: InTypes.VMess, users: <VmessUser[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
trojan: <Trojan>{ type: InTypes.Trojan, users: <NamePass[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
naive: <Naive>{ type: InTypes.Naive, users: <UsernamePass[]>[], tls_id: 0 },
|
||||
hysteria: <Hysteria>{ type: InTypes.Hysteria, users: <NameAuth[]>[], up_mbps: 100, down_mbps: 100, tls_id: 0 },
|
||||
shadowtls: <ShadowTLS>{ type: InTypes.ShadowTLS, version: 3, users: <NamePass[]>[], handshake: {}, handshake_for_server_name: {} },
|
||||
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls: { enabled: true } },
|
||||
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls: { enabled: true } },
|
||||
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls: {}, multiplex: {}, transport: {} },
|
||||
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls_id: 0 },
|
||||
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls_id: 0 },
|
||||
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
tun: <Tun>{ type: InTypes.Tun, mtu: 9000, stack: 'system', udp_timeout: '5m', auto_route: false },
|
||||
redirect: <Redirect>{ type: InTypes.Redirect },
|
||||
tproxy: <TProxy>{ type: InTypes.TProxy },
|
||||
|
||||
@@ -10,7 +10,6 @@ export const OutTypes = {
|
||||
Shadowsocks: 'shadowsocks',
|
||||
VMess: 'vmess',
|
||||
Trojan: 'trojan',
|
||||
Wireguard: 'wireguard',
|
||||
Hysteria: 'hysteria',
|
||||
VLESS: 'vless',
|
||||
ShadowTLS: 'shadowtls',
|
||||
@@ -41,6 +40,7 @@ export interface Dial {
|
||||
}
|
||||
|
||||
interface OutboundBasics {
|
||||
id: number
|
||||
type: OutType
|
||||
tag: string
|
||||
}
|
||||
@@ -124,23 +124,6 @@ export interface Trojan extends OutboundBasics, Dial {
|
||||
transport?: Transport
|
||||
}
|
||||
|
||||
export interface WireGuard extends OutboundBasics, Dial {
|
||||
server?: string
|
||||
server_port?: number
|
||||
system_interface?: boolean
|
||||
gso?: boolean
|
||||
interface_name?: string
|
||||
local_address: string[]
|
||||
private_key: string
|
||||
peers?: WgPeer[]
|
||||
peer_public_key?: string
|
||||
pre_shared_key?: string
|
||||
reserved?: number[]
|
||||
workers?: number
|
||||
mtu?: number
|
||||
network?: "udp" | "tcp"
|
||||
}
|
||||
|
||||
export interface Hysteria extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
@@ -263,7 +246,6 @@ const defaultValues: Record<OutType, Outbound> = {
|
||||
shadowsocks: { type: OutTypes.Shadowsocks, method: 'none', multiplex: {} },
|
||||
vmess: { type: OutTypes.VMess, tls: {}, multiplex: {}, transport: {}, security: 'auto', global_padding: false },
|
||||
trojan: { type: OutTypes.Trojan, tls: {}, multiplex: {}, transport: {} },
|
||||
wireguard: { type: OutTypes.Wireguard, local_address: ['10.0.0.2/32','fe80::2/128'], private_key: '' },
|
||||
hysteria: { type: OutTypes.Hysteria, up_mbps: 100, down_mbps: 100, tls: { enabled: true } },
|
||||
shadowtls: { type: OutTypes.ShadowTLS, version: 3, tls: { enabled: true } },
|
||||
vless: { type: OutTypes.VLESS, tls: {}, multiplex: {}, transport: {} },
|
||||
|
||||
+35
-109
@@ -1,4 +1,11 @@
|
||||
<template>
|
||||
<v-row style="margin-bottom: 10px;">
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn variant="outlined" color="warning" @click="saveConfig" :loading="loading" :disabled="stateChange">
|
||||
{{ $t('actions.save') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel :title="$t('basic.log.title')">
|
||||
<v-expansion-panel-text>
|
||||
@@ -11,6 +18,8 @@
|
||||
hide-details
|
||||
:label="$t('basic.log.level')"
|
||||
:items="levels"
|
||||
clearable
|
||||
@click:clear="delete appConfig.log.level"
|
||||
v-model="appConfig.log.level">
|
||||
</v-select>
|
||||
</v-col>
|
||||
@@ -215,128 +224,49 @@
|
||||
hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
Clash API
|
||||
<v-divider></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="3" lg="2">
|
||||
<v-switch v-model="enableClashApi" color="primary" :label="$t('enable')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.external_controller"
|
||||
hide-details
|
||||
label="External Controller"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.external_ui"
|
||||
hide-details
|
||||
label="External UI"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.external_ui_download_url"
|
||||
hide-details
|
||||
label="UI Download URL"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.external_ui_download_detour"
|
||||
hide-details
|
||||
label="UI Download detour"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.secret"
|
||||
hide-details
|
||||
label="Secret"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.default_mode"
|
||||
hide-details
|
||||
label="Default Mode"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
V2Ray API
|
||||
<v-divider></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="3" lg="2">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.v2ray_api.listen"
|
||||
hide-details
|
||||
:label="$t('objects.listen')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2">
|
||||
<v-switch v-model="appConfig.experimental.v2ray_api.stats.enabled"
|
||||
color="primary"
|
||||
:label="$t('stats.enable')"
|
||||
hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="appConfig.experimental.v2ray_api.stats.enabled">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('pages.inbounds')"
|
||||
multiple chips closable-chips
|
||||
:items="inboundTags"
|
||||
v-model="appConfig.experimental.v2ray_api.stats.inbounds">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('pages.outbounds')"
|
||||
multiple chips closable-chips
|
||||
:items="outboundTags"
|
||||
v-model="appConfig.experimental.v2ray_api.stats.outbounds">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('pages.clients')"
|
||||
multiple chips closable-chips
|
||||
:items="clientNames"
|
||||
v-model="appConfig.experimental.v2ray_api.stats.users">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data';
|
||||
import Dial from '@/components/Dial.vue';
|
||||
import { computed } from 'vue';
|
||||
import { Config, Ntp } from '@/types/config';
|
||||
import { Client } from '@/types/clients';
|
||||
import Data from '@/store/modules/data'
|
||||
import Dial from '@/components/Dial.vue'
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { Config, Ntp } from '@/types/config'
|
||||
import { Client } from '@/types/clients'
|
||||
import { FindDiff } from '@/plugins/utils'
|
||||
|
||||
const oldConfig = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const inboundTags = computed((): string[] => {
|
||||
return appConfig.value.inbounds.map(i => i.tag)
|
||||
onMounted(async () => {
|
||||
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||
})
|
||||
|
||||
const stateChange = computed(() => {
|
||||
return FindDiff.deepCompare(appConfig.value,oldConfig.value)
|
||||
})
|
||||
|
||||
const saveConfig = async () => {
|
||||
loading.value = true
|
||||
const success = await Data().save("config", "set", appConfig.value)
|
||||
if (success) {
|
||||
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const outboundTags = computed((): string[] => {
|
||||
return appConfig.value.outbounds.map(o => o.tag)
|
||||
return [...Data().outbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||
})
|
||||
|
||||
const clientNames = computed((): string[] => {
|
||||
const clients = <Client[]>Data().clients
|
||||
return clients?.map(c => c.name)
|
||||
return Data().clients.map((c:any) => c.name)
|
||||
})
|
||||
|
||||
const levels = ["trace", "debug", "info", "warn", "error", "fatal", "panic"]
|
||||
@@ -384,8 +314,4 @@ const enableCacheFile = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const enableClashApi = computed({
|
||||
get() { return appConfig.value.experimental.clash_api != undefined },
|
||||
set(v:boolean) { v ? appConfig.value.experimental.clash_api = {} : delete appConfig.value.experimental.clash_api }
|
||||
})
|
||||
</script>
|
||||
+30
-112
@@ -3,10 +3,9 @@
|
||||
<ClientModal
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:index="modal.index"
|
||||
:id="modal.id"
|
||||
:data="modal.data"
|
||||
:groups="groups"
|
||||
:stats="modal.stats"
|
||||
:inboundTags="inboundTags"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
@@ -34,7 +33,7 @@
|
||||
/>
|
||||
<v-row justify="center" align="center">
|
||||
<v-col cols="auto">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-menu v-model="actionMenu" :close-on-content-click="false" location="bottom center">
|
||||
@@ -147,7 +146,6 @@
|
||||
<v-col cols="auto">
|
||||
<v-switch color="primary"
|
||||
v-model="item.enable"
|
||||
@update:model-value="buildInboundsUsers(item.inbounds)"
|
||||
hideDetails density="compact" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -162,7 +160,7 @@
|
||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds != ''">
|
||||
<span v-for="i in item.inbounds">{{ i }}<br /></span>
|
||||
<span v-for="i in item.inbounds">{{ inbounds.find(inb => inb.id == i)?.tag }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ item.inbounds.length }}
|
||||
</v-col>
|
||||
@@ -230,7 +228,7 @@
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" text="QR-Code"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.name)" v-if="v2rayStats.users.includes(item.name)">
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.name)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -324,7 +322,7 @@
|
||||
>
|
||||
mdi-qrcode
|
||||
</v-icon>
|
||||
<v-icon icon="mdi-chart-line" @click="showStats(item.name)" v-if="v2rayStats.users.includes(item.name)">
|
||||
<v-icon icon="mdi-chart-line" @click="showStats(item.name)">
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-icon>
|
||||
</template>
|
||||
@@ -349,8 +347,7 @@ import QrCode from '@/layouts/modals/QrCode.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Client, createClient } from '@/types/clients'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Config, V2rayApiStats } from '@/types/config'
|
||||
import { InTypes, Inbound,InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
|
||||
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||
import { Link, LinkUtil } from '@/plugins/link'
|
||||
import { HumanReadable } from '@/plugins/utils'
|
||||
import { i18n } from '@/locales'
|
||||
@@ -367,21 +364,13 @@ const isOnline = (cname: string) => computed(() => {
|
||||
return Data().onlines?.user ? Data().onlines.user.includes(cname) : false
|
||||
})
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental.v2ray_api.stats
|
||||
})
|
||||
|
||||
const inbounds = computed((): Inbound[] => {
|
||||
return <Inbound[]> appConfig.value?.inbounds
|
||||
return <Inbound[]> Data().inbounds?? []
|
||||
})
|
||||
|
||||
const inboundTags = computed((): string[] => {
|
||||
const inboundTags = computed((): any[] => {
|
||||
if (!inbounds.value) return []
|
||||
return inbounds.value?.filter(i => i.tag != "" && Object.hasOwn(i,'users')).map(i => i.tag)
|
||||
return inbounds.value?.filter(i => i.tag != "" && inboundWithUsers.includes(i.type)).map(i => { return { title: i.tag, value: i.id } })
|
||||
})
|
||||
|
||||
const groups = computed((): string[] => {
|
||||
@@ -430,102 +419,44 @@ const groupBy = [
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
id: 0,
|
||||
data: "",
|
||||
stats: false,
|
||||
})
|
||||
|
||||
const delOverlay = ref(new Array<boolean>(clients.value.length).fill(false))
|
||||
|
||||
const showModal = (id: number) => {
|
||||
const index = id == -1 ? -1 : clients.value.findIndex(c => c.id == id)
|
||||
modal.value.index = index
|
||||
modal.value.data = index == -1 ? '' : JSON.stringify(clients.value[index])
|
||||
modal.value.stats = index == -1 ? false : v2rayStats.value.users.includes(clients.value[index].name)
|
||||
const showModal = async (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == 0 ? '' : JSON.stringify(clients.value.findLast(o => o.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:any, stats:boolean) => {
|
||||
const saveModal = async (data:any) => {
|
||||
// Check duplicate name
|
||||
const oldName = modal.value.index != -1 ? clients.value[modal.value.index].name : null
|
||||
const oldName = modal.value.id > 0 ? clients.value.findLast(i => i.id == modal.value.id)?.name : null
|
||||
if (data.name != oldName && clients.value.findIndex(c => c.name == data.name) != -1) {
|
||||
push.error({
|
||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name')
|
||||
})
|
||||
return
|
||||
}
|
||||
if(modal.value.index == -1) {
|
||||
clients.value.push(data)
|
||||
} else {
|
||||
clients.value[modal.value.index] = data
|
||||
}
|
||||
|
||||
// Rebuild affected inbounds
|
||||
buildInboundsUsers(data.inbounds)
|
||||
|
||||
// Rebuild links
|
||||
data.links = updateLinks(data)
|
||||
const clientInbounds = data.inbounds.length == 0 ? [] : await Data().loadInbounds(data.inbounds)
|
||||
data.links = updateLinks(data, clientInbounds)
|
||||
|
||||
// Set Client Stats
|
||||
const sIndex = v2rayStats.value.users.findIndex(i => i == data.name) // Find if new user exists
|
||||
|
||||
if (oldName != data.name) {
|
||||
v2rayStats.value.users = v2rayStats.value.users.filter(item => item != oldName)
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
// Add if dos not exist
|
||||
if (data.name.length>0 && sIndex == -1) v2rayStats.value.users.push(data.name)
|
||||
} else {
|
||||
// Delete if exists
|
||||
if (sIndex != -1) v2rayStats.value.users.splice(sIndex,1)
|
||||
}
|
||||
|
||||
modal.value.visible = false
|
||||
// save data
|
||||
const success = await Data().save("clients", modal.value.id == 0 ? "new" : "edit", data)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
const buildInboundsUsers = (inboundTags:string[]) => {
|
||||
inboundTags.forEach(tag => {
|
||||
const inbound_index = inbounds.value.findIndex(i => i.tag == tag)
|
||||
if (inbound_index != -1){
|
||||
const users = <any>[]
|
||||
const newInbound = <InboundWithUser>inbounds.value[inbound_index]
|
||||
const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(tag))
|
||||
inboundClients.forEach(c => {
|
||||
// Remove flow in non tls VLESS
|
||||
if (newInbound.type == InTypes.VLESS) {
|
||||
const vlessInbound = <VLESS>newInbound
|
||||
if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
|
||||
}
|
||||
users.push(c.config[newInbound.type])
|
||||
})
|
||||
newInbound.users = users
|
||||
|
||||
// Exceptions for Naive and ShadowTLSv3
|
||||
if (users.length == 0){
|
||||
if (newInbound.type == InTypes.Naive) {
|
||||
newInbound.users = <any>[{}]
|
||||
} else {
|
||||
if (newInbound.type == InTypes.ShadowTLS){
|
||||
const ssTls = <ShadowTLS>newInbound
|
||||
if (ssTls.version == 3) newInbound.users = <any>[{}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inbounds.value[inbound_index] = newInbound
|
||||
}
|
||||
})
|
||||
}
|
||||
const updateLinks = (c:Client):Link[] => {
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(i => c.inbounds.includes(i.tag))
|
||||
const updateLinks = (c:Client, clientInbounds:Inbound[]):Link[] => {
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const tlsConfig = <any>Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
|
||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||
const addrs = cData ? <any[]>cData.addrs : []
|
||||
const uris = LinkUtil.linkGenerator(c,i, tlsConfig?.client?? {}, addrs)
|
||||
const tls = i.tls_id && i.tls_id>0 ? Data().tlsConfigs?.findLast((t:any) => t.id == i.tls_id) : undefined
|
||||
const uris = LinkUtil.linkGenerator(c,i, tls, i.addrs)
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
@@ -537,21 +468,10 @@ const updateLinks = (c:Client):Link[] => {
|
||||
|
||||
return links
|
||||
}
|
||||
const delClient = (id: number) => {
|
||||
const clientIndex = clients.value.findIndex(c => c.id === id)
|
||||
const oldData = createClient(clients.value[clientIndex])
|
||||
|
||||
// Delete stats if exists and will be orphaned
|
||||
const tagCounts = clients.value.filter(i => i.name == oldData.name).length
|
||||
const sIndex = v2rayStats.value.users.findIndex(i => i == oldData.name)
|
||||
if (tagCounts == 1 && sIndex != -1){
|
||||
v2rayStats.value.users.splice(sIndex,1)
|
||||
}
|
||||
|
||||
clients.value.splice(clientIndex,1)
|
||||
buildInboundsUsers(oldData.inbounds)
|
||||
if (id>0) Data().delClient(id)
|
||||
delOverlay.value[clientIndex] = false
|
||||
const delClient = async (id: number) => {
|
||||
const index = clients.value.findIndex(c => c.id === id)
|
||||
const success = await Data().save("clients", "del", id)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const qrcode = ref({
|
||||
@@ -636,14 +556,12 @@ const closeBulk = () => {
|
||||
addBulkModal.value = false
|
||||
}
|
||||
|
||||
const saveBulk = (bulkClients: Client[], clientInbounds: string[], clientStats: boolean) => {
|
||||
const saveBulk = async (bulkClients: Client[], clientInbounds: number[]) => {
|
||||
const inboundData = clientInbounds.length == 0 ? [] : await Data().loadInbounds(clientInbounds)
|
||||
bulkClients.forEach((c,c_index) => {
|
||||
bulkClients[c_index].links = updateLinks(c)
|
||||
bulkClients[c_index].links = updateLinks(c, inboundData)
|
||||
})
|
||||
clients.value.push(...bulkClients)
|
||||
buildInboundsUsers(clientInbounds)
|
||||
// Stats
|
||||
if (clientStats) v2rayStats.value.users.push(...bulkClients.map(bc => bc.name))
|
||||
closeBulk()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<EndpointVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:id="modal.id"
|
||||
:data="modal.data"
|
||||
:tags="endpointTags"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
<Stats
|
||||
v-model="stats.visible"
|
||||
:visible="stats.visible"
|
||||
:resource="stats.resource"
|
||||
:tag="stats.tag"
|
||||
@close="closeStats"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>endpoints" :key="item.tag">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.tag">
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.type }}</v-col>
|
||||
</v-row>
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>{{ $t('in.addr') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.address?.length>0 ? item.address[0] : '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('in.port') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.listen_port?? '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('types.wg.peers') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.peers.length?? '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('online') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<template v-if="onlines.includes(item.tag)">
|
||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-overlay
|
||||
v-model="delOverlay[index]"
|
||||
contained
|
||||
class="align-center justify-center"
|
||||
>
|
||||
<v-card :title="$t('actions.del')" rounded="lg">
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delEndpoint(item.tag)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
import EndpointVue from '@/layouts/modals/Endpoint.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Endpoint } from '@/types/endpoints';
|
||||
import { computed, ref } from 'vue'
|
||||
import { i18n } from '@/locales';
|
||||
import { push } from 'notivue';
|
||||
|
||||
const endpoints = computed((): Endpoint[] => {
|
||||
return <Endpoint[]> Data().endpoints
|
||||
})
|
||||
|
||||
const endpointTags = computed((): any[] => {
|
||||
return endpoints.value?.map((o:Endpoint) => o.tag)
|
||||
})
|
||||
|
||||
const onlines = computed(() => {
|
||||
return [...Data().onlines.inbound?? [], ...Data().onlines.outbound??[] ]
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
id: 0,
|
||||
data: "",
|
||||
})
|
||||
|
||||
let delOverlay = ref(new Array<boolean>)
|
||||
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == 0 ? '' : JSON.stringify(endpoints.value.findLast(o => o.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = async (data:Endpoint) => {
|
||||
// Check duplicate tag
|
||||
const oldTag = modal.value.id > 0 ? endpoints.value.findLast(i => i.id == modal.value.id)?.tag : null
|
||||
if (data.tag != oldTag && endpointTags.value.includes(data.tag)) {
|
||||
push.error({
|
||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// save data
|
||||
const success = await Data().save("endpoints", modal.value.id == 0 ? "new" : "edit", data)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
|
||||
const stats = ref({
|
||||
visible: false,
|
||||
resource: "endpoint",
|
||||
tag: "",
|
||||
})
|
||||
|
||||
const delEndpoint = async (tag: string) => {
|
||||
const index = endpoints.value.findIndex(i => i.tag == tag)
|
||||
const success = await Data().save("endpoints", "del", tag)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const showStats = (tag: string) => {
|
||||
stats.value.tag = tag
|
||||
stats.value.visible = true
|
||||
}
|
||||
const closeStats = () => {
|
||||
stats.value.visible = false
|
||||
}
|
||||
</script>
|
||||
+82
-201
@@ -2,10 +2,7 @@
|
||||
<InboundVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:index="modal.index"
|
||||
:stats="modal.stats"
|
||||
:data="modal.data"
|
||||
:cData="modal.cData"
|
||||
:id="modal.id"
|
||||
:inTags="inTags"
|
||||
:outTags="outTags"
|
||||
:tlsConfigs="tlsConfigs"
|
||||
@@ -21,7 +18,7 @@
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -48,22 +45,25 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('objects.tls') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
|
||||
{{ item.tls_id > 0 ? $t('enable') : $t('disable') }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.clients') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="Object.hasOwn(item,'users')">
|
||||
<span v-for="u in findInbounsUsers(item)">{{ u }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ Array.isArray(item.users) ? item.users.length : '-' }}
|
||||
<template v-if="inboundWithUsers.includes(item.tag)">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="findInboundUsers(item.tag).length > 0">
|
||||
<span v-for="u in findInboundUsers(item.tag)">{{ u }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ findInboundUsers(item.tag).length }}
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('online') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<template v-if="onlines[index]">
|
||||
<template v-if="onlines.includes(item.tag)">
|
||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
@@ -72,7 +72,7 @@
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -89,12 +89,12 @@
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delInbound(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="error" variant="outlined" @click="delInbound(item.id)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)" v-if="v2rayStats.inbounds.includes(item.tag)">
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -108,9 +108,9 @@
|
||||
import Data from '@/store/modules/data'
|
||||
import InboundVue from '@/layouts/modals/Inbound.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Config, V2rayApiStats } from '@/types/config'
|
||||
import { computed, ref } from 'vue'
|
||||
import { InTypes, Inbound, InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
|
||||
import { Config } from '@/types/config'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||
import { Client } from '@/types/clients'
|
||||
import { Link, LinkUtil } from '@/plugins/link'
|
||||
import { i18n } from '@/locales'
|
||||
@@ -122,17 +122,13 @@ const appConfig = computed((): Config => {
|
||||
})
|
||||
|
||||
const inbounds = computed((): Inbound[] => {
|
||||
return <Inbound[]> appConfig.value.inbounds
|
||||
return <Inbound[]> Data().inbounds
|
||||
})
|
||||
|
||||
const tlsConfigs = computed((): any[] => {
|
||||
return <any[]> Data().tlsConfigs
|
||||
})
|
||||
|
||||
const inData = computed((): any[] => {
|
||||
return <any[]> Data().inData
|
||||
})
|
||||
|
||||
const inTags = computed((): string[] => {
|
||||
return inbounds.value?.map(i => i.tag)
|
||||
})
|
||||
@@ -149,216 +145,101 @@ const onlines = computed(() => {
|
||||
return Data().onlines.inbound ? inbounds.value.map(i => Data().onlines.inbound.includes(i.tag)) : []
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental?.v2ray_api.stats
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
data: "",
|
||||
cData: "",
|
||||
stats: false,
|
||||
id: 0,
|
||||
})
|
||||
|
||||
let delOverlay = ref(new Array<boolean>)
|
||||
|
||||
const showModal = (index: number) => {
|
||||
modal.value.index = index
|
||||
if (index == -1){
|
||||
modal.value.data = ''
|
||||
modal.value.cData = ''
|
||||
modal.value.stats = false
|
||||
} else {
|
||||
modal.value.data = JSON.stringify(inbounds.value[index])
|
||||
modal.value.stats = v2rayStats.value.inbounds.includes(inbounds.value[index].tag)
|
||||
const inDataIndex = inData.value.findIndex(d => d.tag == inbounds.value[index].tag)
|
||||
modal.value.cData = inDataIndex == -1 ? '' : JSON.stringify(inData.value[inDataIndex])
|
||||
}
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.visible = true
|
||||
}
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:Inbound, stats: boolean, tls_id: number, cData: any) => {
|
||||
const saveModal = async (data:Inbound) => {
|
||||
// Check duplicate tag
|
||||
const oldTag = modal.value.index != -1 ? inbounds.value[modal.value.index].tag : null
|
||||
if (data.tag != oldTag && inTags.value.includes(data.tag)) {
|
||||
const oldInbound = modal.value.id > 0 ? inbounds.value.findLast(i => i.id == modal.value.id) : null
|
||||
if (data.tag != oldInbound?.tag && inTags.value.includes(data.tag)) {
|
||||
push.error({
|
||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||
})
|
||||
return
|
||||
}
|
||||
if (cData.id != -1) {
|
||||
cData.tag = data.tag
|
||||
fillData(cData.outJson, data,tls_id>0 ? tlsConfigs.value.findLast(t => t.id == tls_id).client : {})
|
||||
|
||||
// Fill outjson
|
||||
if (data.out_json){
|
||||
fillData(data, data.tls_id > 0 ? tlsConfigs?.value.findLast((t:any) => t.id == data.tls_id) : null)
|
||||
}
|
||||
|
||||
// New or Edit
|
||||
if (modal.value.index == -1) {
|
||||
inbounds.value.push(data)
|
||||
if (stats && data.tag.length>0) {
|
||||
v2rayStats.value.inbounds.push(data.tag)
|
||||
}
|
||||
if (cData.id != -1){
|
||||
inData.value.push(cData)
|
||||
}
|
||||
} else {
|
||||
const oldTag = inbounds.value[modal.value.index].tag
|
||||
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
||||
|
||||
// Update tls preset
|
||||
const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(oldTag))
|
||||
if (oldTlsConfigIndex != -1){
|
||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
|
||||
}
|
||||
|
||||
if (oldTag != data.tag) {
|
||||
v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
|
||||
changeClientInboundsTag(oldTag,data.tag)
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
// Add if dos not exist
|
||||
if (data.tag.length>0 && sIndex == -1) v2rayStats.value.inbounds.push(data.tag)
|
||||
} else {
|
||||
// Delete if exists
|
||||
if (sIndex != -1) v2rayStats.value.inbounds.splice(sIndex,1)
|
||||
}
|
||||
|
||||
inbounds.value[modal.value.index] = data
|
||||
const inDataIndex = inData.value.findIndex(indata => indata.tag == oldTag)
|
||||
if (cData.id != -1) {
|
||||
if (inDataIndex == -1){
|
||||
inData.value.push(cData)
|
||||
} else {
|
||||
inData.value[inDataIndex] = cData
|
||||
}
|
||||
} else if (inDataIndex != -1) {
|
||||
Data().delInData(inData.value[inDataIndex].id)
|
||||
inData.value.splice(inDataIndex,1)
|
||||
}
|
||||
}
|
||||
// Update tls preset
|
||||
if (tls_id>0) {
|
||||
tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
|
||||
tlsConfigs.value.sort()
|
||||
let userLinkDiff = []
|
||||
// Update links
|
||||
if (data.id > 0 && oldInbound != null) {
|
||||
userLinkDiff = updateLinks(data,oldInbound)
|
||||
}
|
||||
|
||||
if (Object.hasOwn(data,'users')) {
|
||||
// Set users
|
||||
data = buildInboundsUsers(data)
|
||||
// Update links
|
||||
updateLinks(data)
|
||||
}
|
||||
modal.value.visible = false
|
||||
// save data
|
||||
const success = await Data().save("inbounds", modal.value.id == 0 ? "new" : "edit", data, userLinkDiff)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
const updateLinks = (i: any) => {
|
||||
if(i.users){
|
||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
|
||||
uClients.forEach((u:Client) => {
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => u.inbounds.includes(inb.tag))
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? {}
|
||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||
const addrs = cData ? <any[]>cData.addrs : []
|
||||
const uris = LinkUtil.linkGenerator(u,i, tlsClient, addrs)
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
})
|
||||
}
|
||||
})
|
||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
||||
links = [...newLinks, ...links.filter(l => l.type != 'local')]
|
||||
const updateLinks = (i: Inbound, o: Inbound): any[] => {
|
||||
let diff = <any[]>[]
|
||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.id))
|
||||
if (uClients.length == 0) return diff
|
||||
|
||||
u.links = links
|
||||
if (inboundWithUsers.includes(o.type) && !inboundWithUsers.includes(i.type)){
|
||||
// Remove old inbound links if new type does not support users
|
||||
uClients.forEach((u:Client) => {
|
||||
u.inbounds = u.inbounds.filter(i => i != o.id)
|
||||
const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != o.tag)
|
||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
||||
links = [...otherLocalLinks, ...links.filter(l => l.type != 'local')]
|
||||
|
||||
diff.push({ id: u.id, links: links, inbounds: u.inbounds })
|
||||
})
|
||||
} else if(inboundWithUsers.includes(i.type)){
|
||||
// Add new inbound links if new type supports users
|
||||
const tls = tlsConfigs?.value.findLast((t:any) => t.id == i.tls_id)
|
||||
uClients.forEach((u:Client) => {
|
||||
const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != i.tag)
|
||||
const uris = LinkUtil.linkGenerator(u,i, tls, i.addrs)
|
||||
let newLinks = <Link[]>[]
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
})
|
||||
}
|
||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
||||
links = [...otherLocalLinks, ...newLinks, ...links.filter(l => l.type != 'local')]
|
||||
|
||||
diff.push({ id: u.id, links: links, inbounds: u.inbounds })
|
||||
})
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
const delInbound = (index: number) => {
|
||||
const delInbound = async (id: number) => {
|
||||
const index = inbounds.value.findIndex(i => i.id == id)
|
||||
const inb = inbounds.value[index]
|
||||
inbounds.value.splice(index,1)
|
||||
const tag = inb.tag
|
||||
|
||||
if (Object.hasOwn(inb,'users')) {
|
||||
const inbU = <InboundWithUser>inb
|
||||
if (inbU.users && inbU.users.length>0){
|
||||
inbU.users.forEach((u:any) => {
|
||||
const c_index = clients.value.findIndex(c => u.username? u.username == c.name : u.name == c.name)
|
||||
if (c_index != -1) {
|
||||
clients.value[c_index].inbounds = clients.value[c_index].inbounds.filter((x:string) => x!=tag)
|
||||
clients.value[c_index].links = clients.value[c_index].links.filter((x:any) => x.remark!=tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Delete binded tls if exists
|
||||
if (Object.hasOwn(inb,'tls')) {
|
||||
const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(inb.tag))
|
||||
if (oldTlsConfigIndex != -1){
|
||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != inb.tag)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete stats if exists and will be orphaned
|
||||
const tagCounts = inbounds.value.filter(i => i.tag == inb.tag).length
|
||||
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == inb.tag)
|
||||
if (tagCounts == 1 && sIndex != -1){
|
||||
v2rayStats.value.inbounds.splice(sIndex,1)
|
||||
}
|
||||
if (index < Data().oldData.config.inbounds.length){
|
||||
Data().delInbound(index)
|
||||
} else {
|
||||
// Delete new inbound's inData if exists
|
||||
const inDataIndex = Data().inData.findIndex((d:any) => d.tag == tag)
|
||||
if (inDataIndex != -1) Data().inData.splice(inDataIndex, 1)
|
||||
}
|
||||
delOverlay.value[index] = false
|
||||
}
|
||||
const buildInboundsUsers = (inbound:any):Inbound => {
|
||||
const users = <any>[]
|
||||
const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(inbound.tag))
|
||||
inboundClients.forEach(c => {
|
||||
// Remove flow in non tls VLESS
|
||||
if (inbound.type == InTypes.VLESS) {
|
||||
const vlessInbound = <VLESS>inbound
|
||||
if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
|
||||
}
|
||||
users.push(c.config[inbound.type])
|
||||
})
|
||||
inbound.users = users
|
||||
|
||||
// Exceptions for Naive and ShadowTLSv3
|
||||
if (users.length == 0){
|
||||
if (inbound.type == InTypes.Naive){
|
||||
inbound.users = <any>[{}]
|
||||
} else {
|
||||
if (inbound.type == InTypes.ShadowTLS){
|
||||
const ssTls = <ShadowTLS>inbound
|
||||
if (ssTls.version == 3) inbound.users = <any>[{}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <Inbound>inbound
|
||||
}
|
||||
const changeClientInboundsTag = (oldtag: string, newTag:string) => {
|
||||
clients.value.forEach((c, c_index) => {
|
||||
const inbound_index = c.inbounds.findIndex(i => i == oldtag)
|
||||
if (inbound_index != -1) {
|
||||
c.inbounds[inbound_index] = newTag
|
||||
clients.value[c_index].inbounds = c.inbounds
|
||||
}
|
||||
let diff = <any[]>[]
|
||||
// delete inbound in client table
|
||||
const inboundClients = clients.value.filter(c => c.inbounds.includes(id))
|
||||
inboundClients.forEach((c:Client) => {
|
||||
c.inbounds = c.inbounds.filter((x:number) => x!=id)
|
||||
c.links = c.links.filter((x:any) => x.remark!=tag)
|
||||
diff.push({ id: c.id, links: c.links, inbounds: c.inbounds })
|
||||
})
|
||||
}
|
||||
const findInbounsUsers = (inbound: InboundWithUser): string[] => {
|
||||
if (inbound.users === null || !Array.isArray(inbound.users) || inbound.users.length == 0) return []
|
||||
|
||||
const users = inbound.users.map(user => "username" in user ? user.username : user.name)
|
||||
return users
|
||||
const success = await Data().save("inbounds", "del", tag, diff)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const findInboundUsers = (i: Inbound): string[] => {
|
||||
return clients.value.filter(c => c.inbounds.includes(i.id)).map(c => c.name)
|
||||
}
|
||||
|
||||
const stats = ref({
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:id="modal.id"
|
||||
:stats="modal.stats"
|
||||
:data="modal.data"
|
||||
:tags="outboundTags"
|
||||
@close="closeModal"
|
||||
@@ -18,7 +17,7 @@
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -51,7 +50,7 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('online') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<template v-if="onlines[index]">
|
||||
<template v-if="onlines.includes(item.tag)">
|
||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
@@ -60,7 +59,7 @@
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -77,12 +76,12 @@
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delOutbound(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="error" variant="outlined" @click="delOutbound(item.tag)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)" v-if="v2rayStats.outbounds.includes(item.tag)">
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -96,79 +95,56 @@
|
||||
import Data from '@/store/modules/data'
|
||||
import OutboundVue from '@/layouts/modals/Outbound.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Config, V2rayApiStats } from '@/types/config';
|
||||
import { Outbound } from '@/types/outbounds';
|
||||
import { computed, ref } from 'vue'
|
||||
import { i18n } from '@/locales';
|
||||
import { push } from 'notivue';
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const outbounds = computed((): Outbound[] => {
|
||||
return <Outbound[]> appConfig.value.outbounds
|
||||
return <Outbound[]> Data().outbounds
|
||||
})
|
||||
|
||||
const outboundTags = computed((): string[] => {
|
||||
const outboundTags = computed((): any[] => {
|
||||
return outbounds.value?.map((o:Outbound) => o.tag)
|
||||
})
|
||||
|
||||
const onlines = computed(() => {
|
||||
return Data().onlines.outbound ? outbounds.value.map(i => Data().onlines.outbound.includes(i.tag)) : []
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental?.v2ray_api.stats
|
||||
return Data().onlines.outbound?? []
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
id: -1,
|
||||
id: 0,
|
||||
data: "",
|
||||
stats: false,
|
||||
})
|
||||
|
||||
let delOverlay = ref(new Array<boolean>)
|
||||
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == -1 ? '' : JSON.stringify(outbounds.value[id])
|
||||
modal.value.stats = id == -1 ? false : v2rayStats.value.outbounds.includes(outbounds.value[id].tag)
|
||||
modal.value.data = id == 0 ? '' : JSON.stringify(outbounds.value.findLast(o => o.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:Outbound, stats: boolean) => {
|
||||
const saveModal = async (data:Outbound) => {
|
||||
// Check duplicate tag
|
||||
const oldTag = modal.value.id != -1 ? outbounds.value[modal.value.id].tag : null
|
||||
const oldTag = modal.value.id > 0 ? outbounds.value.findLast(i => i.id == modal.value.id)?.tag : null
|
||||
if (data.tag != oldTag && outboundTags.value.includes(data.tag)) {
|
||||
push.error({
|
||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||
})
|
||||
return
|
||||
}
|
||||
// New or Edit
|
||||
if (modal.value.id == -1) {
|
||||
outbounds.value.push(data)
|
||||
if (stats && data.tag.length>0) {
|
||||
v2rayStats.value.outbounds.push(data.tag)
|
||||
}
|
||||
} else {
|
||||
const sIndex = v2rayStats.value.outbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
||||
|
||||
if (stats) {
|
||||
// Add if dos not exist
|
||||
if (data.tag.length>0 && sIndex == -1) v2rayStats.value.outbounds.push(data.tag)
|
||||
} else {
|
||||
// Delete if exists
|
||||
if (sIndex != -1) v2rayStats.value.outbounds.splice(sIndex,1)
|
||||
}
|
||||
|
||||
outbounds.value[modal.value.id] = data
|
||||
// save data
|
||||
const success = await Data().save("outbounds", modal.value.id == 0 ? "new" : "edit", data)
|
||||
if (!success) {
|
||||
return
|
||||
}
|
||||
|
||||
modal.value.visible = false
|
||||
}
|
||||
|
||||
@@ -178,21 +154,10 @@ const stats = ref({
|
||||
tag: "",
|
||||
})
|
||||
|
||||
const delOutbound = (index: number) => {
|
||||
const inb = outbounds.value[index]
|
||||
outbounds.value.splice(index,1)
|
||||
const tag = inb.tag
|
||||
|
||||
// Delete stats if exists and will be orphaned
|
||||
const tagCounts = outbounds.value.filter(i => i.tag == inb.tag).length
|
||||
const sIndex = v2rayStats.value.outbounds.findIndex(i => i == inb.tag)
|
||||
if (tagCounts == 1 && sIndex != -1){
|
||||
v2rayStats.value.outbounds.splice(sIndex,1)
|
||||
}
|
||||
if (index < Data().oldData.config.outbounds.length){
|
||||
Data().delOutbound(index)
|
||||
}
|
||||
delOverlay.value[index] = false
|
||||
const delOutbound = async (tag: string) => {
|
||||
const index = outbounds.value.findIndex(i => i.tag == tag)
|
||||
const success = await Data().save("outbounds", "del", tag)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const showStats = (tag: string) => {
|
||||
@@ -202,4 +167,8 @@ const showStats = (tag: string) => {
|
||||
const closeStats = () => {
|
||||
stats.value.visible = false
|
||||
}
|
||||
|
||||
function awaitData() {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
</script>
|
||||
@@ -24,6 +24,9 @@
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showRuleModal(-1)" style="margin: 0 5px;">{{ $t('rule.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showRulesetModal(-1)" style="margin: 0 5px;">{{ $t('ruleset.add') }}</v-btn>
|
||||
<v-btn variant="outlined" color="warning" @click="saveConfig" :loading="loading" :disabled="stateChange">
|
||||
{{ $t('actions.save') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -144,16 +147,37 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import RuleVue from '@/layouts/modals/Rule.vue'
|
||||
import RulesetVue from '@/layouts/modals/Ruleset.vue'
|
||||
import { Config } from '@/types/config'
|
||||
import { logicalRule, ruleset } from '@/types/rules'
|
||||
import { FindDiff } from '@/plugins/utils'
|
||||
|
||||
const oldConfig = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||
})
|
||||
|
||||
const stateChange = computed(() => {
|
||||
return FindDiff.deepCompare(appConfig.value,oldConfig.value)
|
||||
})
|
||||
|
||||
const saveConfig = async () => {
|
||||
loading.value = true
|
||||
const success = await Data().save("config", "set", appConfig.value)
|
||||
if (success) {
|
||||
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const clients = computed((): string[] => {
|
||||
return Data().clients.map((c:any) => c.name)
|
||||
})
|
||||
@@ -189,11 +213,11 @@ const rulesetTags = computed((): any[] => {
|
||||
})
|
||||
|
||||
const outboundTags = computed((): string[] => {
|
||||
return appConfig.value.outbounds?.map((o:any) => o.tag)
|
||||
return [...Data().outbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||
})
|
||||
|
||||
const inboundTags = computed((): string[] => {
|
||||
return appConfig.value.inbounds?.map((i:any) => i.tag)
|
||||
return [...Data().inbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||
})
|
||||
|
||||
let delRuleOverlay = ref(new Array<boolean>)
|
||||
@@ -268,7 +292,7 @@ const draggedItemIndex = ref(null);
|
||||
|
||||
const onDragStart = (index: any) => {
|
||||
draggedItemIndex.value = index;
|
||||
};
|
||||
}
|
||||
|
||||
const onDrop = (index: any) => {
|
||||
if (draggedItemIndex.value !== null) {
|
||||
@@ -278,5 +302,5 @@ const onDrop = (index: any) => {
|
||||
rules.value.splice(index, 0, draggedItem);
|
||||
draggedItemIndex.value = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
+82
-71
@@ -2,19 +2,19 @@
|
||||
<TlsVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:index="modal.index"
|
||||
:id="modal.id"
|
||||
:data="modal.data"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>tlsConfigs" :key="item.id">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="(item.id? item.id + '. ' : '*') + item.name">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.name">
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
{{ item.server?.server_name?.length>0 ? item.server.server_name : "-" }}
|
||||
</v-card-subtitle>
|
||||
@@ -22,10 +22,13 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds?.length>0">
|
||||
<span v-for="i in item.inbounds">{{ i }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ item.inbounds?.length }}
|
||||
<template v-if="tlsInbounds(item.id).length>0">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom">
|
||||
<span v-for="i in tlsInbounds(item.id)">{{ i }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ tlsInbounds.length }}
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -49,11 +52,11 @@
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn v-if="item.inbounds?.length == 0" icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||
<v-btn v-if="tlsInbounds(item.id).length == 0" icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -66,12 +69,12 @@
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delTls(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="error" variant="outlined" @click="delTls(item.id)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-content-duplicate" @click="clone(index)">
|
||||
<v-btn icon="mdi-content-duplicate" @click="clone(item)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.clone')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -85,8 +88,7 @@
|
||||
import TlsVue from '@/layouts/modals/Tls.vue'
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Config } from '@/types/config'
|
||||
import { Inbound } from '@/types/inbounds'
|
||||
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||
import { Client } from '@/types/clients'
|
||||
import { Link, LinkUtil } from '@/plugins/link'
|
||||
import { fillData } from '@/plugins/outJson'
|
||||
@@ -95,13 +97,13 @@ const tlsConfigs = computed((): any[] => {
|
||||
return Data().tlsConfigs
|
||||
})
|
||||
|
||||
const inbounds = computed((): any[] => {
|
||||
return <any[]>(<Config>Data().config)?.inbounds
|
||||
const inbounds = computed((): Inbound[] => {
|
||||
return Data().inbounds
|
||||
})
|
||||
|
||||
const inData = computed((): any[] => {
|
||||
return <any[]> Data().inData
|
||||
})
|
||||
const tlsInbounds = (id: number): string[] => {
|
||||
return inbounds.value.filter(i => i.tls_id == id).map(i => i.tag)
|
||||
}
|
||||
|
||||
const clients = computed((): any[] => {
|
||||
return <Client[]>Data().clients
|
||||
@@ -109,21 +111,20 @@ const clients = computed((): any[] => {
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
id: 0,
|
||||
data: "",
|
||||
})
|
||||
|
||||
const delOverlay = ref(new Array<boolean>(tlsConfigs.value.length).fill(false))
|
||||
|
||||
const showModal = (index: number) => {
|
||||
modal.value.index = index
|
||||
modal.value.data = index == -1 ? '{}' : JSON.stringify(tlsConfigs.value[index])
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == 0 ? '{}' : JSON.stringify(tlsConfigs.value.findLast(t => t.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
const clone = (index: number) => {
|
||||
let data = JSON.parse(JSON.stringify(tlsConfigs.value[index]))
|
||||
const clone = (obj: any) => {
|
||||
let data = JSON.parse(JSON.stringify(obj))
|
||||
data.id = 0
|
||||
data.inbounds = []
|
||||
while (tlsConfigs.value.findIndex(t => t.name == data.name) != -1){
|
||||
data.name += "-copy"
|
||||
}
|
||||
@@ -132,57 +133,67 @@ const clone = (index: number) => {
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:any) => {
|
||||
const saveModal = async (data:any) => {
|
||||
let outJsons = <any[]>[]
|
||||
let userLinks = <any[]>[]
|
||||
// New or Edit
|
||||
if (modal.value.index == -1) {
|
||||
tlsConfigs.value.push(data)
|
||||
} else {
|
||||
tlsConfigs.value[modal.value.index] = data
|
||||
inbounds?.value.filter(i => tlsConfigs.value[modal.value.index].inbounds.includes(i.tag)).forEach(i =>{
|
||||
if (i.tls != undefined) i.tls = data.server
|
||||
updateInData(i,data.client)
|
||||
updateLinks(i,data.client)
|
||||
})
|
||||
}
|
||||
modal.value.visible = false
|
||||
}
|
||||
|
||||
const delTls = (index: number) => {
|
||||
if (index < Data().oldData.tlsConfigs.length){
|
||||
Data().delTls(tlsConfigs.value[index].id)
|
||||
}
|
||||
tlsConfigs.value.splice(index,1)
|
||||
delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const updateLinks = (i:any,tlsClient:any) => {
|
||||
if(i.users){
|
||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
|
||||
uClients.forEach((client:any) => {
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||
const addrs = cData ? <any[]>cData.addrs : []
|
||||
const uris = LinkUtil.linkGenerator(client,i, tlsClient, addrs)
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
})
|
||||
if (modal.value.id > 0) {
|
||||
const inboundIds = inbounds.value.filter(i => i.tls_id == modal.value.id).map(i => i.id)
|
||||
if (inboundIds.length > 0) {
|
||||
const tlsInbounds = inboundIds.length == 0 ? [] : await Data().loadInbounds(inboundIds)
|
||||
for (const inbound of tlsInbounds) {
|
||||
// Fill outjson
|
||||
if (inbound.out_json) {
|
||||
fillData(inbound, data)
|
||||
}
|
||||
})
|
||||
let links = client.links && client.links.length>0? client.links : <Link[]>[]
|
||||
links = [...newLinks, ...links.filter((l:Link) => l.type != 'local')]
|
||||
outJsons.push({tag: inbound.tag,out_jsons: inbound.out_json})
|
||||
// Update links
|
||||
const diff = updateLinks(inbound)
|
||||
diff.forEach((d: any) => {
|
||||
if (userLinks.findIndex(l => l.id == d.id) == -1) {
|
||||
userLinks.push(d)
|
||||
} else {
|
||||
const index = userLinks.findIndex(l => l.id == d.id)
|
||||
userLinks[index].links = d.links
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
client.links = links
|
||||
}
|
||||
const success = await Data().save("tls", data.id == 0 ? "new" : "edit", data, userLinks.length > 0 ? null: userLinks, outJsons.length > 0 ? null: outJsons)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
|
||||
const delTls = async (id: number) => {
|
||||
const index = tlsConfigs.value.findIndex(t => t.id == id)
|
||||
const success = await Data().save("tls", "del", id)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const updateLinks = (i: Inbound): any[] => {
|
||||
let diff = <any[]>[]
|
||||
if(inboundWithUsers.includes(i.type) && i.id != 0){
|
||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.id))
|
||||
const tlsClient = tlsConfigs?.value.findLast((t:any) => t.id == i.tls_id)
|
||||
uClients.forEach((u:Client) => {
|
||||
const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != i.tag)
|
||||
const uris = LinkUtil.linkGenerator(u,i, tlsClient, i.addrs)
|
||||
let newLinks = <Link[]>[]
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
})
|
||||
}
|
||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
||||
links = [...otherLocalLinks, ...newLinks, ...links.filter(l => l.type != 'local')]
|
||||
|
||||
u.links = links
|
||||
diff.push({ id: u.id, links: links })
|
||||
})
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
const updateInData = (i:any, c:any) => {
|
||||
const inDataIndex = inData.value.findIndex(d => d.tag == i.tag)
|
||||
if (inDataIndex != -1) {
|
||||
fillData(inData.value[inDataIndex].outJson, i, c)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user