diff --git a/sub/default.json b/sub/default.json new file mode 100644 index 00000000..ba13f6fb --- /dev/null +++ b/sub/default.json @@ -0,0 +1,105 @@ +{ + "dns": { + "tag": "dns_out", + "queryStrategy": "UseIP", + "servers": [ + { + "address": "8.8.8.8", + "skipFallback": false + } + ] + }, + "inbounds": [ + { + "port": 10808, + "protocol": "socks", + "settings": { + "auth": "noauth", + "udp": true, + "userLevel": 8 + }, + "sniffing": { + "destOverride": [ + "http", + "tls", + "fakedns" + ], + "enabled": true + }, + "tag": "socks" + }, + { + "port": 10809, + "protocol": "http", + "settings": { + "userLevel": 8 + }, + "tag": "http" + } + ], + "log": { + "loglevel": "warning" + }, + "outbounds": [ + { + "tag": "direct", + "protocol": "freedom", + "settings": { + "domainStrategy": "UseIP" + } + }, + { + "tag": "block", + "protocol": "blackhole", + "settings": { + "response": { + "type": "http" + } + } + } + ], + "policy": { + "levels": { + "8": { + "connIdle": 300, + "downlinkOnly": 1, + "handshake": 4, + "uplinkOnly": 1 + } + }, + "system": { + "statsOutboundUplink": true, + "statsOutboundDownlink": true + } + }, + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "network": "tcp,udp", + "balancerTag": "all" + } + ], + "balancers": [ + { + "tag": "all", + "selector": [ + "proxy" + ], + "strategy": { + "type": "leastPing" + } + } + ] + }, + "observatory": { + "probeInterval": "5m", + "probeURL": "https://api.github.com/_private/browser/stats", + "subjectSelector": [ + "proxy" + ], + "EnableConcurrency": true + }, + "stats": {} +} \ No newline at end of file diff --git a/sub/sub.go b/sub/sub.go index b642f7f2..2a4a37f4 100644 --- a/sub/sub.go +++ b/sub/sub.go @@ -47,11 +47,6 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine := gin.Default() - subPath, err := s.settingService.GetSubPath() - if err != nil { - return nil, err - } - subDomain, err := s.settingService.GetSubDomain() if err != nil { return nil, err @@ -61,9 +56,44 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine.Use(middleware.DomainValidatorMiddleware(subDomain)) } - g := engine.Group(subPath) + LinksPath, err := s.settingService.GetSubPath() + if err != nil { + return nil, err + } - s.sub = NewSUBController(g) + JsonPath, err := s.settingService.GetSubJsonPath() + if err != nil { + return nil, err + } + + Encrypt, err := s.settingService.GetSubEncrypt() + if err != nil { + return nil, err + } + + ShowInfo, err := s.settingService.GetSubShowInfo() + if err != nil { + return nil, err + } + + RemarkModel, err := s.settingService.GetRemarkModel() + if err != nil { + RemarkModel = "-ieo" + } + + SubUpdates, err := s.settingService.GetSubUpdates() + if err != nil { + SubUpdates = "10" + } + + SubJsonFragment, err := s.settingService.GetSubJsonFragment() + if err != nil { + SubJsonFragment = "" + } + + g := engine.Group("/") + + s.sub = NewSUBController(g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment) return engine, nil } diff --git a/sub/subController.go b/sub/subController.go index 5f7c69cf..e0b641df 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -3,34 +3,57 @@ package sub import ( "encoding/base64" "strings" - "x-ui/web/service" "github.com/gin-gonic/gin" ) type SUBController struct { - subService SubService - settingService service.SettingService + subPath string + subJsonPath string + subEncrypt bool + updateInterval string + + subService *SubService + subJsonService *SubJsonService } -func NewSUBController(g *gin.RouterGroup) *SUBController { - a := &SUBController{} +func NewSUBController( + g *gin.RouterGroup, + subPath string, + jsonPath string, + encrypt bool, + showInfo bool, + rModel string, + update string, + jsonFragment string) *SUBController { + + a := &SUBController{ + subPath: subPath, + subJsonPath: jsonPath, + subEncrypt: encrypt, + updateInterval: update, + + subService: NewSubService(showInfo, rModel), + subJsonService: NewSubJsonService(jsonFragment), + } a.initRouter(g) return a } func (a *SUBController) initRouter(g *gin.RouterGroup) { - g = g.Group("/") + gLink := g.Group(a.subPath) + gJson := g.Group(a.subJsonPath) - g.GET("/:subid", a.subs) + gLink.GET(":subid", a.subs) + + gJson.GET(":subid", a.subJsons) } func (a *SUBController) subs(c *gin.Context) { - subEncrypt, _ := a.settingService.GetSubEncrypt() - subShowInfo, _ := a.settingService.GetSubShowInfo() + println(c.Request.Header["User-Agent"][0]) subId := c.Param("subid") host := strings.Split(c.Request.Host, ":")[0] - subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo) + subs, header, err := a.subService.GetSubs(subId, host) if err != nil || len(subs) == 0 { c.String(400, "Error!") } else { @@ -40,14 +63,32 @@ func (a *SUBController) subs(c *gin.Context) { } // 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.Writer.Header().Set("Subscription-Userinfo", header) + c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval) + c.Writer.Header().Set("Profile-Title", subId) - if subEncrypt { + if a.subEncrypt { c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) } else { c.String(200, result) } } } + +func (a *SUBController) subJsons(c *gin.Context) { + println(c.Request.Header["User-Agent"][0]) + subId := c.Param("subid") + host := strings.Split(c.Request.Host, ":")[0] + jsonSub, header, err := a.subJsonService.GetJson(subId, host) + if err != nil || len(jsonSub) == 0 { + c.String(400, "Error!") + } else { + + // Add headers + c.Writer.Header().Set("Subscription-Userinfo", header) + c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval) + c.Writer.Header().Set("Profile-Title", subId) + + c.String(200, jsonSub) + } +} diff --git a/sub/subJsonService.go b/sub/subJsonService.go new file mode 100644 index 00000000..92519f3e --- /dev/null +++ b/sub/subJsonService.go @@ -0,0 +1,355 @@ +package sub + +import ( + _ "embed" + "encoding/json" + "fmt" + "strings" + "x-ui/database/model" + "x-ui/logger" + "x-ui/util/json_util" + "x-ui/util/random" + "x-ui/web/service" + "x-ui/xray" +) + +//go:embed default.json +var defaultJson string + +type SubJsonService struct { + fragmanet string + + inboundService service.InboundService + SubService +} + +func NewSubJsonService(fragment string) *SubJsonService { + return &SubJsonService{ + fragmanet: fragment, + } +} + +func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) { + inbounds, err := s.SubService.getInboundsBySubId(subId) + if err != nil || len(inbounds) == 0 { + return "", "", err + } + + var header string + var traffic xray.ClientTraffic + var clientTraffics []xray.ClientTraffic + var configJson map[string]interface{} + var defaultOutbounds []json_util.RawMessage + + json.Unmarshal([]byte(defaultJson), &configJson) + if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok { + for _, defaultOutbound := range outboundSlices { + jsonBytes, _ := json.Marshal(defaultOutbound) + defaultOutbounds = append(defaultOutbounds, jsonBytes) + } + } + + outbounds := []json_util.RawMessage{} + startIndex := 0 + // Prepare Inbounds + for _, inbound := range inbounds { + clients, err := s.inboundService.GetClients(inbound) + if err != nil { + logger.Error("SubJsonService - GetClients: Unable to get clients from inbound") + } + if clients == nil { + continue + } + if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' { + listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings) + if err == nil { + inbound.Listen = listen + inbound.Port = port + inbound.StreamSettings = streamSettings + } + } + + var subClients []model.Client + for _, client := range clients { + if client.Enable && client.SubID == subId { + subClients = append(subClients, client) + clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email)) + } + } + + outbound := s.getOutbound(inbound, subClients, host, startIndex) + if outbound != nil { + outbounds = append(outbounds, outbound...) + startIndex += len(outbound) + } + } + + if len(outbounds) == 0 { + return "", "", nil + } + + // Prepare statistics + for index, clientTraffic := range clientTraffics { + if index == 0 { + traffic.Up = clientTraffic.Up + traffic.Down = clientTraffic.Down + traffic.Total = clientTraffic.Total + if clientTraffic.ExpiryTime > 0 { + traffic.ExpiryTime = clientTraffic.ExpiryTime + } + } else { + traffic.Up += clientTraffic.Up + traffic.Down += clientTraffic.Down + if traffic.Total == 0 || clientTraffic.Total == 0 { + traffic.Total = 0 + } else { + traffic.Total += clientTraffic.Total + } + if clientTraffic.ExpiryTime != traffic.ExpiryTime { + traffic.ExpiryTime = 0 + } + } + } + + if s.fragmanet != "" { + outbounds = append(outbounds, json_util.RawMessage(s.fragmanet)) + } + + // Combile outbounds + outbounds = append(outbounds, defaultOutbounds...) + configJson["outbounds"] = outbounds + finalJson, _ := json.MarshalIndent(configJson, "", " ") + + header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) + return string(finalJson), header, nil +} + +func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage { + var newOutbounds []json_util.RawMessage + stream := s.streamData(inbound.StreamSettings) + + externalProxies, ok := stream["externalProxy"].([]interface{}) + if !ok || len(externalProxies) == 0 { + externalProxies = []interface{}{ + map[string]interface{}{ + "forceTls": "same", + "dest": host, + "port": float64(inbound.Port), + }, + } + } + + delete(stream, "externalProxy") + + config_index := startIndex + for _, ep := range externalProxies { + extPrxy := ep.(map[string]interface{}) + inbound.Listen = extPrxy["dest"].(string) + inbound.Port = int(extPrxy["port"].(float64)) + newStream := stream + switch extPrxy["forceTls"].(string) { + case "tls": + if newStream["security"] != "tls" { + newStream["security"] = "tls" + newStream["tslSettings"] = map[string]interface{}{} + } + case "none": + if newStream["security"] != "none" { + newStream["security"] = "none" + delete(newStream, "tslSettings") + } + } + streamSettings, _ := json.MarshalIndent(newStream, "", " ") + inbound.StreamSettings = string(streamSettings) + + for _, client := range clients { + inbound.Tag = fmt.Sprintf("proxy_%d", config_index) + switch inbound.Protocol { + case "vmess", "vless": + newOutbounds = append(newOutbounds, s.genVnext(inbound, client)) + case "trojan", "shadowsocks": + newOutbounds = append(newOutbounds, s.genServer(inbound, client)) + } + config_index += 1 + } + } + + return newOutbounds +} + +func (s *SubJsonService) streamData(stream string) map[string]interface{} { + var streamSettings map[string]interface{} + json.Unmarshal([]byte(stream), &streamSettings) + security, _ := streamSettings["security"].(string) + if security == "tls" { + streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{})) + } else if security == "reality" { + streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{})) + } + delete(streamSettings, "sockopt") + + if s.fragmanet != "" { + streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "TcpNoDelay": true}`) + } + + // remove proxy protocol + network, _ := streamSettings["network"].(string) + switch network { + case "tcp": + streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"]) + case "ws": + streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"]) + } + + return streamSettings +} + +func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} { + netSettings, ok := setting.(map[string]interface{}) + if ok { + delete(netSettings, "acceptProxyProtocol") + } + return netSettings +} + +func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} { + tlsData := make(map[string]interface{}, 1) + tlsClientSettings := tData["settings"].(map[string]interface{}) + + tlsData["serverName"] = tData["serverName"] + tlsData["alpn"] = tData["alpn"] + if allowInsecure, ok := tlsClientSettings["allowInsecure"].(string); ok { + tlsData["allowInsecure"] = allowInsecure + } + if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok { + tlsData["fingerprint"] = fingerprint + } + return tlsData +} + +func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} { + rltyData := make(map[string]interface{}, 1) + rltyClientSettings := rData["settings"].(map[string]interface{}) + + rltyData["show"] = false + rltyData["publicKey"] = rltyClientSettings["publicKey"] + rltyData["fingerprint"] = rltyClientSettings["fingerprint"] + + // Set random data + rltyData["spiderX"] = "/" + random.Seq(15) + shortIds, ok := rData["shortIds"].([]interface{}) + if ok && len(shortIds) > 0 { + rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string) + } else { + rltyData["shortId"] = "" + } + serverNames, ok := rData["serverNames"].([]interface{}) + if ok && len(serverNames) > 0 { + rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string) + } else { + rltyData["serverName"] = "" + } + + return rltyData +} + +func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage { + outbound := Outbound{} + usersData := make([]UserVnext, 1) + + usersData[0].ID = client.ID + usersData[0].Level = 8 + if inbound.Protocol == model.VLESS { + usersData[0].Flow = client.Flow + usersData[0].Encryption = "none" + } + + vnextData := make([]VnextSetting, 1) + vnextData[0] = VnextSetting{ + Address: inbound.Listen, + Port: inbound.Port, + Users: usersData, + } + + outbound.Protocol = string(inbound.Protocol) + outbound.Tag = inbound.Tag + outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings) + outbound.Settings = OutboundSettings{ + Vnext: vnextData, + } + + result, _ := json.MarshalIndent(outbound, "", " ") + return result +} + +func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage { + outbound := Outbound{} + + serverData := make([]ServerSetting, 1) + serverData[0] = ServerSetting{ + Address: inbound.Listen, + Port: inbound.Port, + Level: 8, + Password: client.Password, + } + + if inbound.Protocol == model.Shadowsocks { + var inboundSettings map[string]interface{} + json.Unmarshal([]byte(inbound.Settings), &inboundSettings) + method, _ := inboundSettings["method"].(string) + serverData[0].Method = method + + // server password in multi-user 2022 protocols + if strings.HasPrefix(method, "2022") { + if serverPassword, ok := inboundSettings["password"].(string); ok { + serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password) + } + } + } + + outbound.Protocol = string(inbound.Protocol) + outbound.Tag = inbound.Tag + outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings) + outbound.Settings = OutboundSettings{ + Servers: serverData, + } + + result, _ := json.MarshalIndent(outbound, "", " ") + return result +} + +type Outbound struct { + Protocol string `json:"protocol"` + Tag string `json:"tag"` + StreamSettings json_util.RawMessage `json:"streamSettings"` + Mux map[string]interface{} `json:"mux,omitempty"` + ProxySettings map[string]interface{} `json:"proxySettings,omitempty"` + Settings OutboundSettings `json:"settings,omitempty"` +} + +type OutboundSettings struct { + Vnext []VnextSetting `json:"vnext,omitempty"` + Servers []ServerSetting `json:"servers,omitempty"` +} + +type VnextSetting struct { + Address string `json:"address"` + Port int `json:"port"` + Users []UserVnext `json:"users"` +} + +type UserVnext struct { + Encryption string `json:"encryption,omitempty"` + Flow string `json:"flow,omitempty"` + ID string `json:"id"` + Level int `json:"level"` +} + +type ServerSetting struct { + Password string `json:"password"` + Level int `json:"level"` + Address string `json:"address"` + Port int `json:"port"` + Flow string `json:"flow,omitempty"` + Method string `json:"method,omitempty"` +} diff --git a/sub/subService.go b/sub/subService.go index ddf9692b..06d1ed0a 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -25,47 +25,42 @@ type SubService struct { settingService service.SettingService } -func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) { +func NewSubService(showInfo bool, remarkModel string) *SubService { + return &SubService{ + showInfo: showInfo, + remarkModel: remarkModel, + } +} + +func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) { s.address = host - s.showInfo = showInfo var result []string - var headers []string + var header string var traffic xray.ClientTraffic var clientTraffics []xray.ClientTraffic inbounds, err := s.getInboundsBySubId(subId) if err != nil { - return nil, nil, err - } - s.remarkModel, err = s.settingService.GetRemarkModel() - if err != nil { - s.remarkModel = "-ieo" + return nil, "", err } + s.datepicker, err = s.settingService.GetDatepicker() - if err != nil { - s.datepicker = "gregorian" - } + if err != nil { + s.datepicker = "gregorian" + } for _, inbound := range inbounds { clients, err := s.inboundService.GetClients(inbound) if err != nil { - logger.Error("SubService - GetSub: Unable to get clients from inbound") + logger.Error("SubService - GetClients: Unable to get clients from inbound") } if clients == nil { continue } if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' { - fallbackMaster, err := s.getFallbackMaster(inbound.Listen) + listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings) if err == nil { - inbound.Listen = fallbackMaster.Listen - inbound.Port = fallbackMaster.Port - var stream map[string]interface{} - json.Unmarshal([]byte(inbound.StreamSettings), &stream) - var masterStream map[string]interface{} - json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream) - stream["security"] = masterStream["security"] - stream["tlsSettings"] = masterStream["tlsSettings"] - stream["externalProxy"] = masterStream["externalProxy"] - modifiedStream, _ := json.MarshalIndent(stream, "", " ") - inbound.StreamSettings = string(modifiedStream) + inbound.Listen = listen + inbound.Port = port + inbound.StreamSettings = streamSettings } } for _, client := range clients { @@ -76,6 +71,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string } } } + + // Prepare statistics for index, clientTraffic := range clientTraffics { if index == 0 { traffic.Up = clientTraffic.Up @@ -97,11 +94,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string } } } - headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)) - updateInterval, _ := s.settingService.GetSubUpdates() - headers = append(headers, fmt.Sprintf("%d", updateInterval)) - headers = append(headers, subId) - return result, headers, nil + header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) + return result, header, nil } func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { @@ -130,7 +124,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri return xray.ClientTraffic{} } -func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) { +func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) { db := database.GetDB() var inbound *model.Inbound err := db.Model(model.Inbound{}). @@ -138,9 +132,19 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) { Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest). Find(&inbound).Error if err != nil { - return nil, err + return "", 0, "", err } - return inbound, nil + + var stream map[string]interface{} + json.Unmarshal([]byte(streamSettings), &stream) + var masterStream map[string]interface{} + json.Unmarshal([]byte(inbound.StreamSettings), &masterStream) + stream["security"] = masterStream["security"] + stream["tlsSettings"] = masterStream["tlsSettings"] + stream["externalProxy"] = masterStream["externalProxy"] + modifiedStream, _ := json.MarshalIndent(stream, "", " ") + + return inbound.Listen, inbound.Port, string(modifiedStream), nil } func (s *SubService) getLink(inbound *model.Inbound, email string) string { @@ -578,6 +582,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string if sniValue, ok := searchKey(tlsSetting, "serverName"); ok { params["sni"], _ = sniValue.(string) } + tlsSettings, _ := searchKey(tlsSetting, "settings") if tlsSetting != nil { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js index ddf1a0e1..637830e8 100644 --- a/web/assets/js/model/setting.js +++ b/web/assets/js/model/setting.js @@ -28,6 +28,7 @@ class AllSetting { this.subListen = ""; this.subPort = "2096"; this.subPath = "/sub/"; + this.subJsonPath = "/json/"; this.subDomain = ""; this.subCertFile = ""; this.subKeyFile = ""; @@ -35,6 +36,8 @@ class AllSetting { this.subEncrypt = true; this.subShowInfo = false; this.subURI = ''; + this.subJsonURI = ''; + this.subJsonFragment = ''; this.timeLocation = "Asia/Tehran"; diff --git a/web/controller/xray_setting.go b/web/controller/xray_setting.go index 28f55b54..2dddb44b 100644 --- a/web/controller/xray_setting.go +++ b/web/controller/xray_setting.go @@ -81,7 +81,6 @@ func (a *XraySettingController) warp(c *gin.Context) { resp, err = a.XraySettingService.RegWarp(skey, pkey) case "license": license := c.PostForm("license") - println(license) resp, err = a.XraySettingService.SetWarpLicence(license) } diff --git a/web/entity/entity.go b/web/entity/entity.go index 8ab06399..06850128 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -48,6 +48,9 @@ type AllSetting struct { SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` SubURI string `json:"subURI" form:"subURI"` + SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` + SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` + SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` Datepicker string `json:"datepicker" form:"datepicker"` } @@ -105,6 +108,13 @@ func (s *AllSetting) CheckValid() error { s.SubPath += "/" } + if !strings.HasPrefix(s.SubJsonPath, "/") { + s.SubJsonPath = "/" + s.SubJsonPath + } + if !strings.HasSuffix(s.SubJsonPath, "/") { + s.SubJsonPath += "/" + } + _, err := time.LoadLocation(s.TimeLocation) if err != nil { return common.NewError("time location not exist:", s.TimeLocation) diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html index 3bda4c29..7cc317ef 100644 --- a/web/html/common/qrcode_modal.html +++ b/web/html/common/qrcode_modal.html @@ -8,13 +8,23 @@ {{ i18n "pages.inbounds.clickOnQRcode" }} {{ i18n "pages.inbounds.client" }} @@ -82,12 +92,16 @@ }, genSubLink(subID) { return app.subSettings.subURI+subID; + }, + genSubJsonLink(subID) { + return app.subSettings.subJsonURI+subID; } }, updated() { if (qrModal.client && qrModal.client.subId) { qrModal.subId = qrModal.client.subId; this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId)); + this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId)); } qrModal.qrcodes.forEach((element, index) => { this.setQrCode("qrCode-" + index, element.link); @@ -96,4 +110,4 @@ }); -{{end}} +{{end}} \ No newline at end of file diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html index 23f8bd47..c8341651 100644 --- a/web/html/xui/inbound_info_modal.html +++ b/web/html/xui/inbound_info_modal.html @@ -166,7 +166,7 @@