diff --git a/backend/api/api.go b/backend/api/api.go
index 11c16aa..8b16e82 100644
--- a/backend/api/api.go
+++ b/backend/api/api.go
@@ -15,6 +15,7 @@ type APIHandler struct {
service.ConfigService
service.ClientService
service.TlsService
+ service.InDataService
service.PanelService
service.StatsService
service.ServerService
@@ -206,6 +207,10 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
if err != nil {
return "", err
}
+ inData, err := a.InDataService.GetAll()
+ if err != nil {
+ return "", err
+ }
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
if err != nil {
return "", err
@@ -213,6 +218,7 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
data["config"] = *config
data["clients"] = clients
data["tls"] = tlsConfigs
+ data["inData"] = inData
data["subURI"] = subURI
data["onlines"] = onlines
} else {
diff --git a/backend/database/db.go b/backend/database/db.go
index a6b055d..9e7c5b2 100644
--- a/backend/database/db.go
+++ b/backend/database/db.go
@@ -60,6 +60,7 @@ func InitDB(dbPath string) error {
err = db.AutoMigrate(
&model.Setting{},
&model.Tls{},
+ &model.InboundData{},
&model.User{},
&model.Stats{},
&model.Client{},
diff --git a/backend/database/model/model.go b/backend/database/model/model.go
index eca10f9..0004dcc 100644
--- a/backend/database/model/model.go
+++ b/backend/database/model/model.go
@@ -16,6 +16,13 @@ type Tls struct {
Client json.RawMessage `json:"client" form:"client"`
}
+type InboundData struct {
+ Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
+ Tag string `json:"tag" form:"tag"`
+ Addrs json.RawMessage `json:"addrs" form:"addrs"`
+ OutJson json.RawMessage `json:"outJson" form:"outJson"`
+}
+
type User struct {
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Username string `json:"username" form:"username"`
diff --git a/backend/service/config.go b/backend/service/config.go
index d083deb..c5f4e51 100644
--- a/backend/service/config.go
+++ b/backend/service/config.go
@@ -18,6 +18,7 @@ var LastUpdate int64
type ConfigService struct {
ClientService
TlsService
+ InDataService
singbox.Controller
SettingService
}
@@ -80,7 +81,7 @@ func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
var err error
- var clientChanges, tlsChanges, settingChanges, configChanges []model.Changes
+ var clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes
if _, ok := changes["clients"]; ok {
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
if err != nil {
@@ -93,6 +94,12 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
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 {
@@ -128,6 +135,12 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
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 {
@@ -185,14 +198,19 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
// Log changes
dt := time.Now().Unix()
- allChanges := append(append(clientChanges, settingChanges...), append(configChanges, tlsChanges...)...)
- 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
+ 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
+ }
}
LastUpdate = dt
diff --git a/backend/service/inData.go b/backend/service/inData.go
new file mode 100644
index 0000000..296b3bc
--- /dev/null
+++ b/backend/service/inData.go
@@ -0,0 +1,46 @@
+package service
+
+import (
+ "encoding/json"
+ "s-ui/database"
+ "s-ui/database/model"
+
+ "gorm.io/gorm"
+)
+
+type InDataService struct {
+}
+
+func (s *InDataService) GetAll() ([]model.InboundData, error) {
+ db := database.GetDB()
+ inData := []model.InboundData{}
+ err := db.Model(model.InboundData{}).Scan(&inData).Error
+ if err != nil {
+ return nil, err
+ }
+
+ return inData, nil
+}
+
+func (s *InDataService) Save(tx *gorm.DB, changes []model.Changes) error {
+ var err error
+ for _, change := range changes {
+ inData := model.InboundData{}
+ err = json.Unmarshal(change.Obj, &inData)
+ if err != nil {
+ return err
+ }
+ switch change.Action {
+ case "new":
+ err = tx.Create(&inData).Error
+ case "del":
+ err = tx.Where("id = ?", change.Index).Delete(model.InboundData{}).Error
+ default:
+ err = tx.Save(inData).Error
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return err
+}
diff --git a/backend/service/setting.go b/backend/service/setting.go
index f3a2c8d..ea48a9f 100644
--- a/backend/service/setting.go
+++ b/backend/service/setting.go
@@ -36,6 +36,7 @@ var defaultValueMap = map[string]string{
"subEncode": "true",
"subShowInfo": "false",
"subURI": "",
+ "subJsonExt": "",
}
type SettingService struct {
@@ -65,7 +66,7 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) {
}
// Due to security principles
- delete(allSetting, "webSecret")
+ delete(allSetting, "secret")
return &allSetting, nil
}
@@ -347,6 +348,10 @@ func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
return err
}
+func (s *SettingService) GetSubJsonExt() (string, error) {
+ return s.getString("subJsonExt")
+}
+
func (s *SettingService) fileExists(path string) error {
_, err := os.Stat(path)
return err
diff --git a/backend/sub/jsonService.go b/backend/sub/jsonService.go
new file mode 100644
index 0000000..fb72992
--- /dev/null
+++ b/backend/sub/jsonService.go
@@ -0,0 +1,255 @@
+package sub
+
+import (
+ "encoding/json"
+ "fmt"
+ "s-ui/database"
+ "s-ui/database/model"
+ "s-ui/service"
+ "s-ui/util"
+)
+
+const defaultJson = `
+{
+ "inbounds": [
+ {
+ "type": "tun",
+ "inet4_address": "172.19.0.1/30",
+ "mtu": 9000,
+ "auto_route": true,
+ "strict_route": false,
+ "sniff": true,
+ "endpoint_independent_nat": false,
+ "stack": "system",
+ "platform": {
+ "http_proxy": {
+ "enabled": true,
+ "server": "127.0.0.1",
+ "server_port": 2080
+ }
+ }
+ },
+ {
+ "type": "mixed",
+ "listen": "127.0.0.1",
+ "listen_port": 2080,
+ "sniff": true,
+ "users": []
+ }
+ ]
+}
+`
+
+type JsonService struct {
+ service.SettingService
+ LinkService
+}
+
+func (j *JsonService) GetJson(subId string, format string) (*string, error) {
+ var jsonConfig map[string]interface{}
+
+ client, inDatas, err := j.getData(subId)
+ if err != nil {
+ return nil, err
+ }
+
+ outbounds, outTags, err := j.getOutbounds(client.Config, inDatas)
+ if err != nil {
+ return nil, err
+ }
+
+ links := j.LinkService.GetLinks(&client.Links, "external", "")
+ for index, link := range links {
+ json, tag, err := util.GetOutbound(link, index)
+ if err == nil && len(tag) > 0 {
+ *outbounds = append(*outbounds, *json)
+ *outTags = append(*outTags, tag)
+ }
+ }
+
+ j.addDefaultOutbounds(outbounds, outTags)
+
+ err = json.Unmarshal([]byte(defaultJson), &jsonConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ jsonConfig["outbounds"] = outbounds
+
+ // Add other objects from settings
+ j.addOthers(&jsonConfig)
+
+ result, _ := json.MarshalIndent(jsonConfig, " ", " ")
+ resultStr := string(result)
+ return &resultStr, nil
+}
+
+func (j *JsonService) getData(subId string) (*model.Client, *[]model.InboundData, 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)
+ 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
+}
+
+func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]model.InboundData) (*[]map[string]interface{}, *[]string, error) {
+ var outbounds []map[string]interface{}
+ var configs map[string]interface{}
+ var outTags []string
+
+ err := json.Unmarshal(clientConfig, &configs)
+ if err != nil {
+ return nil, nil, err
+ }
+ for _, inData := range *inDatas {
+ if len(inData.OutJson) < 5 {
+ continue
+ }
+ var outbound map[string]interface{}
+ err = json.Unmarshal(inData.OutJson, &outbound)
+ if err != nil {
+ return nil, nil, err
+ }
+ protocol, _ := outbound["type"].(string)
+ config, _ := configs[protocol].(map[string]interface{})
+ for key, value := range config {
+ if key != "alterId" && key != "name" && key != "username" {
+ outbound[key] = value
+ }
+ }
+
+ var addrs []map[string]interface{}
+ err = json.Unmarshal(inData.Addrs, &addrs)
+ if err != nil {
+ return nil, nil, err
+ }
+ tag := outbound["tag"].(string)
+ if len(addrs) == 0 {
+ outTags = append(outTags, tag)
+ outbounds = append(outbounds, outbound)
+ } else {
+ for index, addr := range addrs {
+ // Copy original config
+ newOut := make(map[string]interface{}, len(outbound))
+ for key, value := range outbound {
+ newOut[key] = value
+ }
+ // Change and push copied config
+ newOut["server"] = addr["server"].(string)
+ port := addr["server_port"].(float64)
+ newOut["server_port"] = int(port)
+ newTag := fmt.Sprintf("%d.%s", index+1, tag)
+ outTags = append(outTags, newTag)
+ newOut["tag"] = newTag
+ outbounds = append(outbounds, newOut)
+ }
+ }
+ }
+ return &outbounds, &outTags, nil
+}
+
+func (j *JsonService) addDefaultOutbounds(outbounds *[]map[string]interface{}, outTags *[]string) {
+ outbound := []map[string]interface{}{
+ {
+ "outbounds": append([]string{"auto", "direct"}, *outTags...),
+ "tag": "proxy",
+ "type": "selector",
+ },
+ {
+ "tag": "auto",
+ "type": "urltest",
+ "outbounds": outTags,
+ "url": "http://www.gstatic.com/generate_204",
+ "interval": "10m",
+ "tolerance": 50,
+ },
+ {
+ "type": "direct",
+ "tag": "direct",
+ },
+ {
+ "type": "dns",
+ "tag": "dns-out",
+ },
+ {
+ "type": "block",
+ "tag": "block",
+ },
+ }
+ *outbounds = append(outbound, *outbounds...)
+}
+
+func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
+ rules := []interface{}{
+ map[string]interface{}{
+ "type": "logical",
+ "mode": "or",
+ "rules": []interface{}{
+ map[string]interface{}{
+ "port": 53,
+ },
+ map[string]interface{}{
+ "protocol": "dns",
+ },
+ },
+ "outbound": "dns-out",
+ },
+ map[string]interface{}{
+ "clash_mode": "Direct",
+ "outbound": "direct",
+ },
+ map[string]interface{}{
+ "clash_mode": "Global",
+ "outbound": "proxy",
+ },
+ }
+ route := map[string]interface{}{
+ "auto_detect_interface": true,
+ "final": "proxy",
+ "rules": rules,
+ }
+
+ othersStr, err := j.SettingService.GetSubJsonExt()
+ if err != nil {
+ return err
+ }
+ if len(othersStr) == 0 {
+ (*jsonConfig)["route"] = route
+ return nil
+ }
+ var othersJson map[string]interface{}
+ err = json.Unmarshal([]byte(othersStr), &othersJson)
+ if err != nil {
+ return err
+ }
+ if _, ok := othersJson["log"]; ok {
+ (*jsonConfig)["log"] = othersJson["log"]
+ }
+ if _, ok := othersJson["dns"]; ok {
+ (*jsonConfig)["dns"] = othersJson["dns"]
+ }
+ if _, ok := othersJson["experimental"]; ok {
+ (*jsonConfig)["experimental"] = othersJson["lexperimentalog"]
+ }
+ if _, ok := othersJson["rule_set"]; ok {
+ route["rule_set"] = othersJson["rule_set"]
+ }
+ if settingRules, ok := othersJson["rules"].([]interface{}); ok {
+ route["rules"] = append(rules, settingRules...)
+ }
+ (*jsonConfig)["route"] = route
+
+ return nil
+}
diff --git a/backend/sub/linkService.go b/backend/sub/linkService.go
new file mode 100644
index 0000000..b6145b7
--- /dev/null
+++ b/backend/sub/linkService.go
@@ -0,0 +1,100 @@
+package sub
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "io"
+ "net/http"
+ "s-ui/logger"
+ "s-ui/util"
+ "strings"
+)
+
+type Link struct {
+ Type string `json:"type"`
+ Remark string `json:"remark"`
+ Uri string `json:"uri"`
+}
+
+type LinkService struct {
+}
+
+func (s *LinkService) GetLinks(linkJson *json.RawMessage, types string, clientInfo string) []string {
+ links := []Link{}
+ var result []string
+ err := json.Unmarshal(*linkJson, &links)
+ if err != nil {
+ return nil
+ }
+ for _, link := range links {
+ switch link.Type {
+ case "external":
+ result = append(result, link.Uri)
+ case "sub":
+ result = append(result, s.getExternalSub(link.Uri)...)
+ case "local":
+ if types == "all" {
+ result = append(result, s.addClientInfo(link.Uri, clientInfo))
+ }
+ }
+ }
+ return result
+}
+
+func (s *LinkService) addClientInfo(uri string, clientInfo string) string {
+ protocol := strings.Split(uri, "://")
+ if len(protocol) < 2 {
+ return uri
+ }
+ switch protocol[0] {
+ case "vmess":
+ var vmessJson map[string]interface{}
+ config, err := util.B64StrToByte(protocol[1])
+ if err != nil {
+ logger.Warning("sub: Error decoding vmess content:", err)
+ return uri
+ }
+ err = json.Unmarshal(config, &vmessJson)
+ if err != nil {
+ logger.Warning("sub: Error decoding vmess content:", err)
+ return uri
+ }
+ vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo
+ result, err := json.MarshalIndent(vmessJson, "", " ")
+ if err != nil {
+ logger.Warning("sub: Error decoding vmess + clientInfo content:", err)
+ return uri
+ }
+ return "vmess://" + util.ByteToB64Str(result)
+ default:
+ return uri + clientInfo
+ }
+}
+
+func (s *LinkService) getExternalSub(url string) []string {
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+
+ client := &http.Client{Transport: tr}
+
+ // Make the HTTP request
+ response, err := client.Get(url)
+ if err != nil {
+ logger.Warning("sub: Error making HTTP request:", err)
+ return nil
+ }
+ defer response.Body.Close()
+
+ // Read the response body
+ body, err := io.ReadAll(response.Body)
+ if err != nil {
+ logger.Warning("sub: Error reading response body:", err)
+ return nil
+ }
+
+ // Convert if the content is Base64 encoded
+ links := util.StrOrBase64Encoded(string(body))
+ return strings.Split(links, "\n")
+
+}
diff --git a/backend/sub/subHandler.go b/backend/sub/subHandler.go
index 5a97817..0ff672f 100644
--- a/backend/sub/subHandler.go
+++ b/backend/sub/subHandler.go
@@ -10,6 +10,7 @@ import (
type SubHandler struct {
service.SettingService
SubService
+ JsonService
}
func NewSubHandler(g *gin.RouterGroup) {
@@ -23,17 +24,28 @@ func (s *SubHandler) initRouter(g *gin.RouterGroup) {
func (s *SubHandler) subs(c *gin.Context) {
subId := c.Param("subid")
- result, headers, err := s.SubService.GetSubs(subId)
- if err != nil || result == nil {
- logger.Error(err)
- c.String(400, "Error!")
+ format, isFormat := c.GetQuery("format")
+ if isFormat {
+ result, err := s.JsonService.GetJson(subId, format)
+ if err != nil || result == nil {
+ logger.Error(err)
+ c.String(400, "Error!")
+ } else {
+ c.String(200, *result)
+ }
} else {
+ result, headers, err := s.SubService.GetSubs(subId)
+ if err != nil || result == nil {
+ logger.Error(err)
+ c.String(400, "Error!")
+ } else {
- // Add headers
- c.Writer.Header().Set("Subscription-Userinfo", headers[0])
- c.Writer.Header().Set("Profile-Update-Interval", headers[1])
- c.Writer.Header().Set("Profile-Title", headers[2])
+ // Add headers
+ c.Writer.Header().Set("Subscription-Userinfo", headers[0])
+ c.Writer.Header().Set("Profile-Update-Interval", headers[1])
+ c.Writer.Header().Set("Profile-Title", headers[2])
- c.String(200, *result)
+ c.String(200, *result)
+ }
}
}
diff --git a/backend/sub/subService.go b/backend/sub/subService.go
index 0dcf18d..8909d85 100644
--- a/backend/sub/subService.go
+++ b/backend/sub/subService.go
@@ -1,15 +1,10 @@
package sub
import (
- "crypto/tls"
"encoding/base64"
- "encoding/json"
"fmt"
- "io"
- "net/http"
"s-ui/database"
"s-ui/database/model"
- "s-ui/logger"
"s-ui/service"
"strings"
"time"
@@ -17,12 +12,7 @@ import (
type SubService struct {
service.SettingService
-}
-
-type Link struct {
- Type string `json:"type"`
- Remark string `json:"remark"`
- Uri string `json:"uri"`
+ LinkService
}
func (s *SubService) GetSubs(subId string) (*string, []string, error) {
@@ -35,29 +25,14 @@ func (s *SubService) GetSubs(subId string) (*string, []string, error) {
return nil, nil, err
}
- links := []Link{}
- err = json.Unmarshal([]byte(client.Links), &links)
- if err != nil {
- return nil, nil, err
- }
-
clientInfo := ""
subShowInfo, _ := s.SettingService.GetSubShowInfo()
if subShowInfo {
clientInfo = s.getClientInfo(client)
}
- var result string
- for _, link := range links {
- switch link.Type {
- case "external":
- result += fmt.Sprintln(link.Uri)
- case "sub":
- result += s.getExternalSub(link.Uri)
- case "local":
- result += fmt.Sprintln(s.addClientInfo(link.Uri, clientInfo))
- }
- }
+ linksArray := s.LinkService.GetLinks(&client.Links, "all", clientInfo)
+ result := strings.Join(linksArray, "\n")
var headers []string
updateInterval, _ := s.SettingService.GetSubUpdates()
@@ -90,80 +65,6 @@ func (s *SubService) getClientInfo(c *model.Client) string {
}
}
-func (s *SubService) addClientInfo(uri string, clientInfo string) string {
- protocol := strings.Split(uri, "://")
- if len(protocol) < 2 {
- return uri
- }
- switch protocol[0] {
- case "vmess":
- var vmessJson map[string]interface{}
- config, err := base64.StdEncoding.DecodeString(protocol[1])
- if err != nil {
- logger.Warning("sub: Error decoding vmess content:", err)
- return uri
- }
- err = json.Unmarshal(config, &vmessJson)
- if err != nil {
- logger.Warning("sub: Error decoding vmess content:", err)
- return uri
- }
- vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo
- result, err := json.MarshalIndent(vmessJson, "", " ")
- if err != nil {
- logger.Warning("sub: Error decoding vmess + clientInfo content:", err)
- return uri
- }
- return "vmess://" + base64.StdEncoding.EncodeToString(result)
- default:
- return uri + clientInfo
- }
-}
-
-func (s *SubService) getExternalSub(url string) string {
- tr := &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- }
-
- client := &http.Client{Transport: tr}
-
- // Make the HTTP request
- response, err := client.Get(url)
- if err != nil {
- logger.Warning("sub: Error making HTTP request:", err)
- return ""
- }
- defer response.Body.Close()
-
- // Read the response body
- body, err := io.ReadAll(response.Body)
- if err != nil {
- logger.Warning("sub: Error reading response body:", err)
- return ""
- }
-
- // Check if the content is Base64 encoded
- isBase64 := s.isBase64Encoded(string(body))
- if isBase64 {
- // Decode Base64 content
- decodedText, err := base64.StdEncoding.DecodeString(string(body))
- if err != nil {
- logger.Warning("sub: Error decoding Base64 content:", err)
- return ""
- }
-
- return string(decodedText)
- } else {
- return string(body)
- }
-}
-
-// Function to check if a string is Base64 encoded
-func (s *SubService) isBase64Encoded(str string) bool {
- _, err := base64.StdEncoding.DecodeString(str)
- return err == nil
-}
-
func (s *SubService) formatTraffic(trafficBytes int64) string {
if trafficBytes < 1024 {
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
diff --git a/backend/util/base64.go b/backend/util/base64.go
new file mode 100644
index 0000000..c2327b0
--- /dev/null
+++ b/backend/util/base64.go
@@ -0,0 +1,20 @@
+package util
+
+import "encoding/base64"
+
+// Function to return decoded bytes if a string is Base64 encoded
+func StrOrBase64Encoded(str string) string {
+ decoded, err := base64.StdEncoding.DecodeString(str)
+ if err == nil {
+ return string(decoded)
+ }
+ return str
+}
+
+func B64StrToByte(str string) ([]byte, error) {
+ return base64.StdEncoding.DecodeString(str)
+}
+
+func ByteToB64Str(b []byte) string {
+ return base64.StdEncoding.EncodeToString(b)
+}
diff --git a/backend/util/linkToJson.go b/backend/util/linkToJson.go
new file mode 100644
index 0000000..d78324d
--- /dev/null
+++ b/backend/util/linkToJson.go
@@ -0,0 +1,453 @@
+package util
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "net/url"
+ "s-ui/util/common"
+ "strconv"
+ "strings"
+)
+
+func GetOutbound(uri string, i int) (*map[string]interface{}, string, error) {
+ u, err := url.Parse(uri)
+ if err == nil {
+ switch u.Scheme {
+ case "vmess":
+ return vmess(u.Host, i)
+ case "vless":
+ return vless(u, i)
+ case "trojan":
+ return trojan(u, i)
+ case "hy", "hysteria":
+ return hy(u, i)
+ case "hy2", "hysteria2":
+ return hy2(u, i)
+ case "tuic":
+ return tuic(u, i)
+ case "ss", "shadowsocks":
+ return ss(u, i)
+ }
+ }
+ return nil, "", common.NewError("Unsupported link format")
+}
+
+func vmess(data string, i int) (*map[string]interface{}, string, error) {
+ dataByte, err := B64StrToByte(data)
+ if err != nil {
+ return nil, "", err
+ }
+ var dataJson map[string]interface{}
+ err = json.Unmarshal(dataByte, &dataJson)
+ if err != nil {
+ return nil, "", err
+ }
+ transport := map[string]interface{}{}
+ tp_net, _ := dataJson["net"].(string)
+ tp_type, _ := dataJson["type"].(string)
+ tp_host, _ := dataJson["host"].(string)
+ tp_path, _ := dataJson["path"].(string)
+ switch strings.ToLower(tp_net) {
+ case "tcp", "":
+ if tp_type == "http" {
+ transport["type"] = tp_type
+ if len(tp_host) > 0 {
+ transport["host"] = strings.Split(tp_host, ",")
+ }
+ transport["path"] = tp_path
+ }
+ case "http", "h2":
+ transport["type"] = "http"
+ if len(tp_host) > 0 {
+ transport["host"] = strings.Split(tp_host, ",")
+ }
+ transport["path"] = tp_path
+ case "ws":
+ transport["type"] = tp_net
+ transport["path"] = tp_path
+ transport["early_data_header_name"] = "Sec-WebSocket-Protocol"
+ if len(tp_host) > 0 {
+ transport["headers"] = map[string]interface{}{
+ "Host": tp_host,
+ }
+ }
+ case "quic":
+ transport["type"] = tp_net
+ case "grpc":
+ transport["type"] = tp_net
+ transport["service_name"] = tp_path
+ case "httpupgrade":
+ transport["type"] = tp_net
+ transport["path"] = tp_path
+ transport["host"] = tp_host
+ default:
+ return nil, "", common.NewError("Invalid vmess")
+ }
+ tls := map[string]interface{}{}
+ vmess_tls, _ := dataJson["tls"].(string)
+ if vmess_tls == "tls" {
+ tls["enabled"] = true
+ tls_sni, _ := dataJson["sni"].(string)
+ tls_alpn, _ := dataJson["alpn"].(string)
+ _, tls_insecure := dataJson["allowInsecure"]
+ tls_fp, _ := dataJson["fp"].(string)
+ if len(tls_sni) > 0 {
+ tls["server_name"] = tls_sni
+ }
+ if len(tls_alpn) > 0 {
+ tls["alpn"] = strings.Split(tls_alpn, ",")
+ }
+ if tls_insecure {
+ tls["insecure"] = true
+ }
+ if len(tls_fp) > 0 {
+ tls["utls"] = map[string]interface{}{
+ "enabled": true,
+ "fingerprint": tls_fp,
+ }
+ }
+ }
+ tag, _ := dataJson["ps"].(string)
+ tag = fmt.Sprintf("%d.%s", i, tag)
+ alter_id, ok := dataJson["aid"].(int)
+ if !ok {
+ alter_id = 0
+ }
+ vmess := map[string]interface{}{
+ "type": "vmess",
+ "tag": tag,
+ "server": dataJson["add"],
+ "server_port": dataJson["port"],
+ "uuid": dataJson["id"],
+ "security": "auto",
+ "alter_id": alter_id,
+ "tls": tls,
+ "transport": transport,
+ }
+ return &vmess, tag, err
+}
+
+func vless(u *url.URL, i int) (*map[string]interface{}, string, error) {
+ query, _ := url.ParseQuery(u.RawQuery)
+ security := query.Get("security")
+ host, portStr, _ := net.SplitHostPort(u.Host)
+ port := 80
+ if len(portStr) > 0 {
+ port, _ = strconv.Atoi(portStr)
+ } else {
+ if security == "tls" || security == "reality" {
+ port = 443
+ }
+ }
+ tp_type := query.Get("type")
+ tag := fmt.Sprintf("%d.%s", i, u.Fragment)
+ vless := map[string]interface{}{
+ "type": "vless",
+ "tag": tag,
+ "server": host,
+ "server_port": port,
+ "uuid": u.User.Username(),
+ "flow": query.Get("flow"),
+ "tls": getTls(security, &query),
+ "transport": getTransport(tp_type, &query),
+ }
+ return &vless, tag, nil
+}
+
+func trojan(u *url.URL, i int) (*map[string]interface{}, string, error) {
+ query, _ := url.ParseQuery(u.RawQuery)
+ security := query.Get("security")
+ host, portStr, _ := net.SplitHostPort(u.Host)
+ port := 80
+ if len(portStr) > 0 {
+ port, _ = strconv.Atoi(portStr)
+ } else {
+ if security == "tls" || security == "reality" {
+ port = 443
+ }
+ }
+ tp_type := query.Get("type")
+ tag := fmt.Sprintf("%d.%s", i, u.Fragment)
+ trojan := map[string]interface{}{
+ "type": "trojan",
+ "tag": tag,
+ "server": host,
+ "server_port": port,
+ "password": u.User.Username(),
+ "tls": getTls(security, &query),
+ "transport": getTransport(tp_type, &query),
+ }
+ return &trojan, tag, nil
+}
+
+func hy(u *url.URL, i int) (*map[string]interface{}, string, error) {
+ query, _ := url.ParseQuery(u.RawQuery)
+ host, portStr, _ := net.SplitHostPort(u.Host)
+ port := 443
+ if len(portStr) > 0 {
+ port, _ = strconv.Atoi(portStr)
+ }
+
+ tls := map[string]interface{}{
+ "enabled": true,
+ "server_name": query.Get("peer"),
+ }
+ alpn := query.Get("alpn")
+ insecure := query.Get("insecure")
+ if len(alpn) > 0 {
+ tls["alpn"] = strings.Split(alpn, ",")
+ }
+ if insecure == "1" || insecure == "true" {
+ tls["insecure"] = true
+ }
+
+ tag := fmt.Sprintf("%d.%s", i, u.Fragment)
+ hy := map[string]interface{}{
+ "type": "hysteria",
+ "tag": tag,
+ "server": host,
+ "server_port": port,
+ "obfs": query.Get("obfsParam"),
+ "auth_str": query.Get("auth"),
+ "tls": tls,
+ }
+ down, _ := strconv.Atoi(query.Get("downmbps"))
+ up, _ := strconv.Atoi(query.Get("upmbps"))
+ recv_window_conn, _ := strconv.Atoi(query.Get("recv_window_conn"))
+ recv_window, _ := strconv.Atoi(query.Get("recv_window"))
+ if down > 0 {
+ hy["down_mbps"] = down
+ }
+ if up > 0 {
+ hy["up_mbps"] = up
+ }
+ if recv_window_conn > 0 {
+ hy["recv_window_conn"] = recv_window_conn
+ }
+ if recv_window > 0 {
+ hy["recv_window"] = recv_window
+ }
+ return &hy, tag, nil
+}
+
+func hy2(u *url.URL, i int) (*map[string]interface{}, string, error) {
+ query, _ := url.ParseQuery(u.RawQuery)
+ host, portStr, _ := net.SplitHostPort(u.Host)
+ port := 443
+ if len(portStr) > 0 {
+ port, _ = strconv.Atoi(portStr)
+ }
+
+ tls := map[string]interface{}{
+ "enabled": true,
+ "server_name": query.Get("sni"),
+ }
+ alpn := query.Get("alpn")
+ insecure := query.Get("insecure")
+ if len(alpn) > 0 {
+ tls["alpn"] = strings.Split(alpn, ",")
+ }
+ if insecure == "1" || insecure == "true" {
+ tls["insecure"] = true
+ }
+
+ tag := fmt.Sprintf("%d.%s", i, u.Fragment)
+ hy2 := map[string]interface{}{
+ "type": "hysteria2",
+ "tag": tag,
+ "server": host,
+ "server_port": port,
+ "password": u.User.Username(),
+ "tls": tls,
+ }
+ down, _ := strconv.Atoi(query.Get("downmbps"))
+ up, _ := strconv.Atoi(query.Get("upmbps"))
+ obfs := query.Get("obfs")
+ if down > 0 {
+ hy2["down_mbps"] = down
+ }
+ if up > 0 {
+ hy2["up_mbps"] = up
+ }
+ if obfs == "salamander" {
+ hy2["obfs"] = map[string]interface{}{
+ "type": "salamander",
+ "password": query.Get("obfs-password"),
+ }
+ }
+ return &hy2, tag, nil
+}
+
+func tuic(u *url.URL, i int) (*map[string]interface{}, string, error) {
+ query, _ := url.ParseQuery(u.RawQuery)
+ host, portStr, _ := net.SplitHostPort(u.Host)
+ port := 443
+ if len(portStr) > 0 {
+ port, _ = strconv.Atoi(portStr)
+ }
+
+ tls := map[string]interface{}{
+ "enabled": true,
+ "server_name": query.Get("sni"),
+ }
+ alpn := query.Get("alpn")
+ insecure := query.Get("allow_insecure")
+ disable_sni := query.Get("disable_sni")
+ if len(alpn) > 0 {
+ tls["alpn"] = strings.Split(alpn, ",")
+ }
+ if insecure == "1" || insecure == "true" {
+ tls["insecure"] = true
+ }
+ if disable_sni == "1" || disable_sni == "true" {
+ tls["disable_sni"] = true
+ }
+
+ tag := fmt.Sprintf("%d.%s", i, u.Fragment)
+ password, _ := u.User.Password()
+ tuic := map[string]interface{}{
+ "type": "tuic",
+ "tag": tag,
+ "server": host,
+ "server_port": port,
+ "uuid": u.User.Username(),
+ "password": password,
+ "congestion_control": query.Get("congestion_control"),
+ "udp_relay_mode": query.Get("udp_relay_mode"),
+ "tls": tls,
+ }
+ return &tuic, tag, nil
+}
+
+func ss(u *url.URL, i int) (*map[string]interface{}, string, error) {
+ query, _ := url.ParseQuery(u.RawQuery)
+ host, portStr, _ := net.SplitHostPort(u.Host)
+ port := 443
+ if len(portStr) > 0 {
+ port, _ = strconv.Atoi(portStr)
+ }
+ method := u.User.Username()
+ password, ok := u.User.Password()
+ if !ok {
+ decrypted := StrOrBase64Encoded(method)
+ decrypted_arr := strings.Split(decrypted, ":")
+ if len(decrypted_arr) > 1 {
+ method = decrypted_arr[0]
+ password = strings.Join(decrypted_arr[1:], ":")
+ } else {
+ return nil, "", common.NewError("Unsupported shadowsocks")
+ }
+ }
+
+ tag := fmt.Sprintf("%d.%s", i, u.Fragment)
+ ss := map[string]interface{}{
+ "type": "shadowsocks",
+ "tag": tag,
+ "server": host,
+ "server_port": port,
+ "method": method,
+ "password": password,
+ }
+
+ v2ray_type := query.Get("type")
+ if len(v2ray_type) > 0 {
+ pl_arr := []string{}
+ host_header := query.Get("host")
+ if query.Get("security") == "tls" {
+ pl_arr = append(pl_arr, "tls")
+ }
+ if v2ray_type == "quic" {
+ pl_arr = append(pl_arr, "mode=quic")
+ }
+ if len(host_header) > 0 {
+ pl_arr = append(pl_arr, "host="+host_header)
+ }
+ ss["plugin"] = "v2ray-plugin"
+ ss["plugin_opts"] = strings.Join(pl_arr, ";")
+ }
+ plugin := query.Get("plugin")
+ if len(plugin) > 0 {
+ pl_arr := strings.Split(plugin, ";")
+ if len(pl_arr) > 0 {
+ ss["plugin"] = pl_arr[0]
+ ss["plugin_opts"] = strings.Join(pl_arr[1:], ";")
+ }
+ }
+ return &ss, tag, nil
+}
+
+func getTransport(tp_type string, q *url.Values) *map[string]interface{} {
+ transport := map[string]interface{}{}
+ tp_host := q.Get("host")
+ tp_path := q.Get("path")
+ switch strings.ToLower(tp_type) {
+ case "tcp", "":
+ if q.Get("headerType") == "http" {
+ transport["type"] = "http"
+ if len(tp_host) > 0 {
+ transport["host"] = strings.Split(tp_host, ",")
+ }
+ transport["path"] = tp_path
+ }
+ case "http", "h2":
+ transport["type"] = "http"
+ if len(tp_host) > 0 {
+ transport["host"] = strings.Split(tp_host, ",")
+ }
+ transport["path"] = tp_path
+ case "ws":
+ transport["type"] = "ws"
+ transport["path"] = tp_path
+ if len(tp_host) > 0 {
+ transport["headers"] = map[string]interface{}{
+ "Host": tp_host,
+ }
+ }
+ case "quic":
+ transport["type"] = "quic"
+ case "grpc":
+ transport["type"] = "grpc"
+ transport["service_name"] = q.Get("serviceName")
+ case "httpupgrade":
+ transport["type"] = "httpupgrade"
+ transport["path"] = tp_path
+ transport["host"] = tp_host
+ }
+ return &transport
+}
+
+func getTls(security string, q *url.Values) *map[string]interface{} {
+ tls := map[string]interface{}{}
+ tls_fp := q.Get("fp")
+ tls_sni := q.Get("sni")
+ tls_insecure := q.Get("allowInsecure")
+ tls_alpn := q.Get("alpn")
+ switch security {
+ case "tls":
+ tls["enabled"] = true
+ case "reality":
+ tls["enabled"] = true
+ tls["reality"] = map[string]interface{}{
+ "enabled": true,
+ "public_key": q.Get("pbk"),
+ "short_id": q.Get("sid"),
+ }
+ }
+ if len(tls_sni) > 0 {
+ tls["server_name"] = tls_sni
+ }
+ if len(tls_alpn) > 0 {
+ tls["alpn"] = strings.Split(tls_alpn, ",")
+ }
+ if tls_insecure == "1" || tls_insecure == "true" {
+ tls["insecure"] = true
+ }
+ if len(tls_fp) > 0 {
+ tls["utls"] = map[string]interface{}{
+ "enabled": true,
+ "fingerprint": tls_fp,
+ }
+ }
+ return &tls
+}
diff --git a/frontend/src/components/Addr.vue b/frontend/src/components/Addr.vue
new file mode 100644
index 0000000..a6341db
--- /dev/null
+++ b/frontend/src/components/Addr.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Address Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/OutJson.vue b/frontend/src/components/OutJson.vue
new file mode 100644
index 0000000..35dbf74
--- /dev/null
+++ b/frontend/src/components/OutJson.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/SubJsonExt.vue b/frontend/src/components/SubJsonExt.vue
new file mode 100644
index 0000000..75a0666
--- /dev/null
+++ b/frontend/src/components/SubJsonExt.vue
@@ -0,0 +1,342 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/Acme.vue b/frontend/src/components/tls/Acme.vue
similarity index 98%
rename from frontend/src/components/Acme.vue
rename to frontend/src/components/tls/Acme.vue
index 9fcc3d3..fea944f 100644
--- a/frontend/src/components/Acme.vue
+++ b/frontend/src/components/tls/Acme.vue
@@ -124,7 +124,7 @@
- {{ $t('tls.acme.options') }}
+ {{ $t('tls.acme.options') }}
diff --git a/frontend/src/components/Ech.vue b/frontend/src/components/tls/Ech.vue
similarity index 100%
rename from frontend/src/components/Ech.vue
rename to frontend/src/components/tls/Ech.vue
diff --git a/frontend/src/components/InTLS.vue b/frontend/src/components/tls/InTLS.vue
similarity index 98%
rename from frontend/src/components/InTLS.vue
rename to frontend/src/components/tls/InTLS.vue
index 4159651..d13768d 100644
--- a/frontend/src/components/InTLS.vue
+++ b/frontend/src/components/tls/InTLS.vue
@@ -116,7 +116,7 @@
- {{ $t('tls.options') }}
+ {{ $t('tls.options') }}
diff --git a/frontend/src/components/OutTLS.vue b/frontend/src/components/tls/OutTLS.vue
similarity index 99%
rename from frontend/src/components/OutTLS.vue
rename to frontend/src/components/tls/OutTLS.vue
index 7a58da5..5fcf2a2 100644
--- a/frontend/src/components/OutTLS.vue
+++ b/frontend/src/components/tls/OutTLS.vue
@@ -177,7 +177,7 @@
- {{ $t('tls.options') }}
+ {{ $t('tls.options') }}
diff --git a/frontend/src/layouts/default/AppBar.vue b/frontend/src/layouts/default/AppBar.vue
index 6b3d0e5..e0ed2f2 100644
--- a/frontend/src/layouts/default/AppBar.vue
+++ b/frontend/src/layouts/default/AppBar.vue
@@ -32,11 +32,11 @@ const saveChanges = () => {
}
const oldData = computed((): any => {
- return {config: store.oldData.config, clients: store.oldData.clients, tls: store.oldData.tlsConfigs}
+ 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}
+ return {config: store.config, clients: store.clients, tls: store.tlsConfigs, inData: store.inData}
})
const stateChange = computed((): any => {
diff --git a/frontend/src/layouts/modals/Inbound.vue b/frontend/src/layouts/modals/Inbound.vue
index c9719bf..828f036 100644
--- a/frontend/src/layouts/modals/Inbound.vue
+++ b/frontend/src/layouts/modals/Inbound.vue
@@ -5,35 +5,66 @@
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Server Side
+ Client Side
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Multi Domain
+
+
+
+ Address #{{ (index+1) }}
+
+
+
+
+ {{ inData }}
+
+
+
@@ -59,6 +90,9 @@
\ No newline at end of file
diff --git a/frontend/src/layouts/modals/Outbound.vue b/frontend/src/layouts/modals/Outbound.vue
index e9faa45..3067886 100644
--- a/frontend/src/layouts/modals/Outbound.vue
+++ b/frontend/src/layouts/modals/Outbound.vue
@@ -89,7 +89,7 @@ import RandomUtil from '@/plugins/randomUtil'
import Dial from '@/components/Dial.vue'
import Multiplex from '@/components/Multiplex.vue'
import Transport from '@/components/Transport.vue'
-import OutTLS from '@/components/OutTLS.vue'
+import OutTLS from '@/components/tls/OutTLS.vue'
import Direct from '@/components/protocols/Direct.vue'
import Socks from '@/components/protocols/Socks.vue'
import Http from '@/components/protocols/Http.vue'
diff --git a/frontend/src/layouts/modals/Tls.vue b/frontend/src/layouts/modals/Tls.vue
index 068c953..5c3219d 100644
--- a/frontend/src/layouts/modals/Tls.vue
+++ b/frontend/src/layouts/modals/Tls.vue
@@ -289,8 +289,8 @@