[sub] json + fragment

Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
This commit is contained in:
MHSanaei 2024-02-21 14:17:52 +03:30
parent f3eb4f055d
commit 03b7a34793
21 changed files with 781 additions and 104 deletions

105
sub/default.json Normal file
View file

@ -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": {}
}

View file

@ -47,11 +47,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine := gin.Default() engine := gin.Default()
subPath, err := s.settingService.GetSubPath()
if err != nil {
return nil, err
}
subDomain, err := s.settingService.GetSubDomain() subDomain, err := s.settingService.GetSubDomain()
if err != nil { if err != nil {
return nil, err return nil, err
@ -61,9 +56,44 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine.Use(middleware.DomainValidatorMiddleware(subDomain)) 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 return engine, nil
} }

View file

@ -3,34 +3,57 @@ package sub
import ( import (
"encoding/base64" "encoding/base64"
"strings" "strings"
"x-ui/web/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type SUBController struct { type SUBController struct {
subService SubService subPath string
settingService service.SettingService subJsonPath string
subEncrypt bool
updateInterval string
subService *SubService
subJsonService *SubJsonService
} }
func NewSUBController(g *gin.RouterGroup) *SUBController { func NewSUBController(
a := &SUBController{} 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) a.initRouter(g)
return a return a
} }
func (a *SUBController) initRouter(g *gin.RouterGroup) { 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) { func (a *SUBController) subs(c *gin.Context) {
subEncrypt, _ := a.settingService.GetSubEncrypt() println(c.Request.Header["User-Agent"][0])
subShowInfo, _ := a.settingService.GetSubShowInfo()
subId := c.Param("subid") subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0] host := strings.Split(c.Request.Host, ":")[0]
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo) subs, header, err := a.subService.GetSubs(subId, host)
if err != nil || len(subs) == 0 { if err != nil || len(subs) == 0 {
c.String(400, "Error!") c.String(400, "Error!")
} else { } else {
@ -40,14 +63,32 @@ func (a *SUBController) subs(c *gin.Context) {
} }
// Add headers // Add headers
c.Writer.Header().Set("Subscription-Userinfo", headers[0]) c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", headers[1]) c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
c.Writer.Header().Set("Profile-Title", headers[2]) c.Writer.Header().Set("Profile-Title", subId)
if subEncrypt { if a.subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} else { } else {
c.String(200, result) 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)
}
}

355
sub/subJsonService.go Normal file
View file

@ -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"`
}

View file

@ -25,21 +25,24 @@ type SubService struct {
settingService service.SettingService 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.address = host
s.showInfo = showInfo
var result []string var result []string
var headers []string var header string
var traffic xray.ClientTraffic var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId) inbounds, err := s.getInboundsBySubId(subId)
if err != nil { if err != nil {
return nil, nil, err return nil, "", err
}
s.remarkModel, err = s.settingService.GetRemarkModel()
if err != nil {
s.remarkModel = "-ieo"
} }
s.datepicker, err = s.settingService.GetDatepicker() s.datepicker, err = s.settingService.GetDatepicker()
if err != nil { if err != nil {
s.datepicker = "gregorian" s.datepicker = "gregorian"
@ -47,25 +50,17 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound) clients, err := s.inboundService.GetClients(inbound)
if err != nil { if err != nil {
logger.Error("SubService - GetSub: Unable to get clients from inbound") logger.Error("SubService - GetClients: Unable to get clients from inbound")
} }
if clients == nil { if clients == nil {
continue continue
} }
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' { 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 { if err == nil {
inbound.Listen = fallbackMaster.Listen inbound.Listen = listen
inbound.Port = fallbackMaster.Port inbound.Port = port
var stream map[string]interface{} inbound.StreamSettings = streamSettings
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)
} }
} }
for _, client := range clients { 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 { for index, clientTraffic := range clientTraffics {
if index == 0 { if index == 0 {
traffic.Up = clientTraffic.Up 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)) header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
updateInterval, _ := s.settingService.GetSubUpdates() return result, header, nil
headers = append(headers, fmt.Sprintf("%d", updateInterval))
headers = append(headers, subId)
return result, headers, nil
} }
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { 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{} 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() db := database.GetDB()
var inbound *model.Inbound var inbound *model.Inbound
err := db.Model(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). Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
Find(&inbound).Error Find(&inbound).Error
if err != nil { 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 { 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 { if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string) params["sni"], _ = sniValue.(string)
} }
tlsSettings, _ := searchKey(tlsSetting, "settings") tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil { if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {

View file

@ -28,6 +28,7 @@ class AllSetting {
this.subListen = ""; this.subListen = "";
this.subPort = "2096"; this.subPort = "2096";
this.subPath = "/sub/"; this.subPath = "/sub/";
this.subJsonPath = "/json/";
this.subDomain = ""; this.subDomain = "";
this.subCertFile = ""; this.subCertFile = "";
this.subKeyFile = ""; this.subKeyFile = "";
@ -35,6 +36,8 @@ class AllSetting {
this.subEncrypt = true; this.subEncrypt = true;
this.subShowInfo = false; this.subShowInfo = false;
this.subURI = ''; this.subURI = '';
this.subJsonURI = '';
this.subJsonFragment = '';
this.timeLocation = "Asia/Tehran"; this.timeLocation = "Asia/Tehran";

View file

@ -81,7 +81,6 @@ func (a *XraySettingController) warp(c *gin.Context) {
resp, err = a.XraySettingService.RegWarp(skey, pkey) resp, err = a.XraySettingService.RegWarp(skey, pkey)
case "license": case "license":
license := c.PostForm("license") license := c.PostForm("license")
println(license)
resp, err = a.XraySettingService.SetWarpLicence(license) resp, err = a.XraySettingService.SetWarpLicence(license)
} }

View file

@ -48,6 +48,9 @@ type AllSetting struct {
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubURI string `json:"subURI" form:"subURI"` 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"` Datepicker string `json:"datepicker" form:"datepicker"`
} }
@ -105,6 +108,13 @@ func (s *AllSetting) CheckValid() error {
s.SubPath += "/" s.SubPath += "/"
} }
if !strings.HasPrefix(s.SubJsonPath, "/") {
s.SubJsonPath = "/" + s.SubJsonPath
}
if !strings.HasSuffix(s.SubJsonPath, "/") {
s.SubJsonPath += "/"
}
_, err := time.LoadLocation(s.TimeLocation) _, err := time.LoadLocation(s.TimeLocation)
if err != nil { if err != nil {
return common.NewError("time location not exist:", s.TimeLocation) return common.NewError("time location not exist:", s.TimeLocation)

View file

@ -8,13 +8,23 @@
{{ i18n "pages.inbounds.clickOnQRcode" }} {{ i18n "pages.inbounds.clickOnQRcode" }}
</a-tag> </a-tag>
<template v-if="app.subSettings.enable && qrModal.subId"> <template v-if="app.subSettings.enable && qrModal.subId">
<a-divider>Subscription</a-divider> <a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas></div> <canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
id="qrCode-sub"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
id="qrCode-subJson"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
</template> </template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider> <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<template v-for="(row, index) in qrModal.qrcodes"> <template v-for="(row, index) in qrModal.qrcodes">
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag> <a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div> <canvas @click="copyToClipboard('qrCode-'+index, row.link)"
:id="'qrCode-'+index"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
</template> </template>
</a-modal> </a-modal>
@ -82,12 +92,16 @@
}, },
genSubLink(subID) { genSubLink(subID) {
return app.subSettings.subURI+subID; return app.subSettings.subURI+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID;
} }
}, },
updated() { updated() {
if (qrModal.client && qrModal.client.subId) { if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId; qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId)); this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
} }
qrModal.qrcodes.forEach((element, index) => { qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link); this.setQrCode("qrCode-" + index, element.link);

View file

@ -166,7 +166,7 @@
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId"> <template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription URL</a-divider> <a-divider>Subscription URL</a-divider>
<a-row> <a-row>
<a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col> <a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right;"> <a-col :sx="24" :md="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"> <button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
@ -175,6 +175,16 @@
</a-tooltip> </a-tooltip>
</a-col> </a-col>
</a-row> </a-row>
<a-row>
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right; margin-top: 5px;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</template> </template>
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId"> <template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram ID</a-divider> <a-divider>Telegram ID</a-divider>
@ -345,6 +355,7 @@
index: null, index: null,
isExpired: false, isExpired: false,
subLink: '', subLink: '',
subJsonLink: '',
show(dbInbound, index) { show(dbInbound, index) {
this.index = index; this.index = index;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
@ -360,6 +371,7 @@
if (this.clientSettings) { if (this.clientSettings) {
if (this.clientSettings.subId) { if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId); this.subLink = this.genSubLink(this.clientSettings.subId);
this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
} }
} }
this.visible = true; this.visible = true;
@ -369,6 +381,9 @@
}, },
genSubLink(subID) { genSubLink(subID) {
return app.subSettings.subURI+subID; return app.subSettings.subURI+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID;
} }
}; };

View file

@ -64,11 +64,6 @@
> >
</a-alert> </a-alert>
</transition> </transition>
<transition name="list" appear>
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
</a-tag>
</transition>
<transition name="list" appear> <transition name="list" appear>
<a-card hoverable> <a-card hoverable>
<a-row> <a-row>
@ -576,7 +571,8 @@
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000, refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
subSettings: { subSettings: {
enable : false, enable : false,
subURI : '' subURI : '',
subJsonURI : '',
}, },
remarkModel: '-ieo', remarkModel: '-ieo',
datepicker: 'gregorian', datepicker: 'gregorian',
@ -623,7 +619,8 @@
this.tgBotEnable = tgBotEnable; this.tgBotEnable = tgBotEnable;
this.subSettings = { this.subSettings = {
enable : subEnable, enable : subEnable,
subURI: subURI subURI: subURI,
subJsonURI: subJsonURI
}; };
this.pageSize = pageSize; this.pageSize = pageSize;
this.remarkModel = remarkModel; this.remarkModel = remarkModel;
@ -997,7 +994,7 @@
}, },
delInbound(dbInboundId) { delInbound(dbInboundId) {
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.inbounds.deleteInbound"}}', title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}', content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}', okText: '{{ i18n "delete"}}',
@ -1010,7 +1007,7 @@
clientId = this.getClientId(dbInbound.protocol, client); clientId = this.getClientId(dbInbound.protocol, client);
if (confirmation){ if (confirmation){
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.inbounds.deleteClient"}}', title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
content: '{{ i18n "pages.inbounds.deleteClientContent"}}', content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}', okText: '{{ i18n "delete"}}',
@ -1301,7 +1298,7 @@
pagination(obj){ pagination(obj){
if (this.pageSize > 0 && obj.length>this.pageSize) { if (this.pageSize > 0 && obj.length>this.pageSize) {
// Set page options based on object size // Set page options based on object size
sizeOptions = [] sizeOptions = [];
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) { for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
sizeOptions.push(i.toString()); sizeOptions.push(i.toString());
} }
@ -1314,8 +1311,8 @@
position: 'bottom', position: 'bottom',
pageSize: this.pageSize, pageSize: this.pageSize,
pageSizeOptions: sizeOptions pageSizeOptions: sizeOptions
} };
return p return p;
} }
return false return false
}, },
@ -1369,7 +1366,6 @@
} }
}, },
}); });
</script> </script>
{{template "inboundModal"}} {{template "inboundModal"}}
@ -1379,6 +1375,5 @@
{{template "inboundInfoModal"}} {{template "inboundInfoModal"}}
{{template "clientsModal"}} {{template "clientsModal"}}
{{template "clientsBulkModal"}} {{template "clientsBulkModal"}}
</body> </body>
</html> </html>

View file

@ -83,6 +83,16 @@
show-icon closable show-icon closable
> >
</a-alert> </a-alert>
<a-alert type="error" v-if="confAlerts.length>0" style="margin-bottom: 10px"
message='{{ i18n "secAlertTitle" }}'
color="red"
show-icon closable
>
<template slot="description">
{{ i18n "secAlertConf" }}
<li v-for="a in confAlerts">- [[ a ]]</li>
</template>
</a-alert>
</transition> </transition>
<a-space direction="vertical"> <a-space direction="vertical">
<a-card hoverable style="margin-bottom: .5rem; overflow-x: hidden;"> <a-card hoverable style="margin-bottom: .5rem; overflow-x: hidden;">
@ -93,9 +103,7 @@
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button> <a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
</a-space> </a-space>
</a-col> </a-col>
<a-col :xs="24" :sm="14"> <a-col :xs="24" :sm="16">
<template>
<div>
<template> <template>
<div> <div>
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"> <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
@ -106,8 +114,6 @@
> >
</div> </div>
</template> </template>
</div>
</template>
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
@ -173,7 +179,6 @@
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<a-list-item-meta title="Language" /> <a-list-item-meta title="Language" />
</a-col> </a-col>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template> <template>
<a-select <a-select
@ -243,7 +248,6 @@
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button> <a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'> <a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
<a-list item-layout="horizontal"> <a-list item-layout="horizontal">
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
@ -266,8 +270,7 @@
ref="selectBotLang" ref="selectBotLang"
v-model="allSetting.tgLang" v-model="allSetting.tgLang"
:dropdown-class-name="themeSwitcher.currentTheme" :dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%" style="width: 100%">
>
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs"> <a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
<span role="img" :aria-label="l.name" v-text="l.icon"></span> <span role="img" :aria-label="l.name" v-text="l.icon"></span>
&nbsp;&nbsp;<span v-text="l.name"></span> &nbsp;&nbsp;<span v-text="l.name"></span>
@ -294,6 +297,17 @@
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
</a-list> </a-list>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
<a-list item-layout="horizontal">
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
<template v-if="fragment">
<setting-list-item type="text" title='length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
</template>
</a-list>
</a-tab-pane>
</a-tabs> </a-tabs>
</a-space> </a-space>
</a-spin> </a-spin>
@ -324,6 +338,24 @@
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'], remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}], datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
remarkSample: '', remarkSample: '',
defaultFragment: {
tag: "fragment",
protocol: "freedom",
settings: {
domainStrategy: "AsIs",
fragment: {
packets: "tlshello",
length: "100-200",
interval: "10-20"
}
},
streamSettings: {
sockopt: {
tcpKeepAliveIdle: 100,
TcpNoDelay: true
}
}
},
get remarkModel() { get remarkModel() {
rm = this.allSetting.remarkModel; rm = this.allSetting.remarkModel;
return rm.length>1 ? rm.substring(1).split('') : []; return rm.length>1 ? rm.substring(1).split('') : [];
@ -452,6 +484,50 @@
} }
}, },
}, },
computed: {
fragment: {
get: function() { return this.allSetting?.subJsonFragment != ""; },
set: function (v) {
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
}
},
fragmentLength: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.length = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentInterval: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.interval = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
confAlerts: {
get: function() {
if (!this.allSetting) return [];
var alerts = []
if (this.allSetting.port == 54321) alerts.push('{{ i18n "pages.settings.panelPort"}}');
panelPath = window.location.pathname.split('/').length<4
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "pages.settings.panelSettings"}} {{ i18n "pages.settings.panelUrlPath"}}');
if (this.allSetting.subEnable) {
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
if (subPath == '/sub/') alerts.push('{{ i18n "pages.settings.subSettings"}} {{ i18n "pages.settings.subPath"}}');
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
if (subJsonPath == '/json/') alerts.push('JSON {{ i18n "pages.settings.subPath"}}');
}
return alerts
}
}
},
async mounted() { async mounted() {
if (window.location.protocol !== "https:") { if (window.location.protocol !== "https:") {
this.showAlert = true; this.showAlert = true;

View file

@ -57,6 +57,9 @@ var defaultValueMap = map[string]string{
"subEncrypt": "true", "subEncrypt": "true",
"subShowInfo": "true", "subShowInfo": "true",
"subURI": "", "subURI": "",
"subJsonPath": "/json/",
"subJsonURI": "",
"subJsonFragment": "",
"datepicker": "gregorian", "datepicker": "gregorian",
"warp": "", "warp": "",
} }
@ -387,17 +390,11 @@ func (s *SettingService) GetSubPort() (int, error) {
} }
func (s *SettingService) GetSubPath() (string, error) { func (s *SettingService) GetSubPath() (string, error) {
subPath, err := s.getString("subPath") return s.getString("subPath")
if err != nil {
return "", err
} }
if !strings.HasPrefix(subPath, "/") {
subPath = "/" + subPath func (s *SettingService) GetSubJsonPath() (string, error) {
} return s.getString("subJsonPath")
if !strings.HasSuffix(subPath, "/") {
subPath += "/"
}
return subPath, nil
} }
func (s *SettingService) GetSubDomain() (string, error) { func (s *SettingService) GetSubDomain() (string, error) {
@ -412,8 +409,8 @@ func (s *SettingService) GetSubKeyFile() (string, error) {
return s.getString("subKeyFile") return s.getString("subKeyFile")
} }
func (s *SettingService) GetSubUpdates() (int, error) { func (s *SettingService) GetSubUpdates() (string, error) {
return s.getInt("subUpdates") return s.getString("subUpdates")
} }
func (s *SettingService) GetSubEncrypt() (bool, error) { func (s *SettingService) GetSubEncrypt() (bool, error) {
@ -432,6 +429,14 @@ func (s *SettingService) GetSubURI() (string, error) {
return s.getString("subURI") return s.getString("subURI")
} }
func (s *SettingService) GetSubJsonURI() (string, error) {
return s.getString("subJsonURI")
}
func (s *SettingService) GetSubJsonFragment() (string, error) {
return s.getString("subJsonFragment")
}
func (s *SettingService) GetDatepicker() (string, error) { func (s *SettingService) GetDatepicker() (string, error) {
return s.getString("datepicker") return s.getString("datepicker")
} }
@ -484,6 +489,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() }, "tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
"subEnable": func() (interface{}, error) { return s.GetSubEnable() }, "subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() }, "subURI": func() (interface{}, error) { return s.GetSubURI() },
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() }, "remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
"datepicker": func() (interface{}, error) { return s.GetDatepicker() }, "datepicker": func() (interface{}, error) { return s.GetDatepicker() },
} }
@ -498,10 +504,11 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
result[key] = value result[key] = value
} }
if result["subEnable"].(bool) && result["subURI"].(string) == "" { if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
subURI := "" subURI := ""
subPort, _ := s.GetSubPort() subPort, _ := s.GetSubPort()
subPath, _ := s.GetSubPath() subPath, _ := s.GetSubPath()
subJsonPath, _ := s.GetSubJsonPath()
subDomain, _ := s.GetSubDomain() subDomain, _ := s.GetSubDomain()
subKeyFile, _ := s.GetSubKeyFile() subKeyFile, _ := s.GetSubKeyFile()
subCertFile, _ := s.GetSubCertFile() subCertFile, _ := s.GetSubCertFile()
@ -522,12 +529,12 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
} else { } else {
subURI += fmt.Sprintf("%s:%d", subDomain, subPort) subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
} }
if subPath[0] == byte('/') { if result["subURI"].(string) == "" {
subURI += subPath result["subURI"] = subURI + subPath
} else { }
subURI += "/" + subPath if result["subJsonURI"].(string) == "" {
result["subJsonURI"] = subURI + subJsonPath
} }
result["subURI"] = subURI
} }
return result, nil return result, nil

View file

@ -54,6 +54,7 @@
"security" = "Security" "security" = "Security"
"secAlertTitle" = "Security Alert" "secAlertTitle" = "Security Alert"
"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection." "secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection."
"secAlertConf" = "Certain configurations have been identified as susceptible to attacks, prompting immediate action to reinforce security protocols and safeguard against potential security breaches."
[menu] [menu]
"dashboard" = "Overview" "dashboard" = "Overview"
@ -304,6 +305,8 @@
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps." "subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
"subURI" = "Reverse Proxy URI" "subURI" = "Reverse Proxy URI"
"subURIDesc" = "The URI path of the subscription URL for use behind proxies." "subURIDesc" = "The URI path of the subscription URL for use behind proxies."
"fragment" = "Fragmentation"
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
[pages.xray] [pages.xray]
"title" = "Xray Configs" "title" = "Xray Configs"

View file

@ -54,6 +54,7 @@
"security" = "Seguridad" "security" = "Seguridad"
"secAlertTitle" = "Alerta de seguridad" "secAlertTitle" = "Alerta de seguridad"
"secAlertSsl" = "Esta conexión no es segura. Evite ingresar información confidencial hasta que TLS esté activado para la protección de datos." "secAlertSsl" = "Esta conexión no es segura. Evite ingresar información confidencial hasta que TLS esté activado para la protección de datos."
"secAlertConf" = "Se han identificado ciertas configuraciones como susceptibles a ataques, lo que genera acciones inmediatas para reforzar los protocolos de seguridad y proteger contra posibles violaciones de seguridad."
[menu] [menu]
"dashboard" = "Estado del Sistema" "dashboard" = "Estado del Sistema"
@ -304,6 +305,8 @@
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración." "subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
"subURI" = "URI de proxy inverso" "subURI" = "URI de proxy inverso"
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy" "subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
"fragment" = "Fragmentación"
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo TLS"
[pages.xray] [pages.xray]
"title" = "Xray Configuración" "title" = "Xray Configuración"

View file

@ -54,6 +54,7 @@
"security" = "امنیت" "security" = "امنیت"
"secAlertTitle" = "هشدار‌امنیتی" "secAlertTitle" = "هشدار‌امنیتی"
"secAlertSsl" = "این‌اتصال‌امن نیست. لطفا‌ تازمانی‌که تی‌ال‌اس برای محافظت از‌ داده‌ها فعال نشده‌است، از وارد کردن اطلاعات حساس خودداری کنید" "secAlertSsl" = "این‌اتصال‌امن نیست. لطفا‌ تازمانی‌که تی‌ال‌اس برای محافظت از‌ داده‌ها فعال نشده‌است، از وارد کردن اطلاعات حساس خودداری کنید"
"secAlertConf" = "پیکربندی‌های خاصی مستعد حملات سایبری شناسایی شده‌اند، اقدام فوری برای تقویت پروتکل‌های امنیتی و محافظت در برابر نقض‌های امنیتی لازم است"
[menu] [menu]
"dashboard" = "نمای کلی" "dashboard" = "نمای کلی"
@ -304,6 +305,8 @@
"subShowInfoDesc" = "ترافیک و زمان باقی‌مانده را در برنامه‌های کاربری نمایش می‌دهد" "subShowInfoDesc" = "ترافیک و زمان باقی‌مانده را در برنامه‌های کاربری نمایش می‌دهد"
"subURI" = "پروکسی معکوس URI مسیر" "subURI" = "پروکسی معکوس URI مسیر"
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسی‌ها تغییر می‌دهد URI مسیر" "subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسی‌ها تغییر می‌دهد URI مسیر"
"fragment" = "تکه‌تکه شدن"
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تی‌ال‌اس"
[pages.xray] [pages.xray]
"title" = "پیکربندی ایکس‌ری" "title" = "پیکربندی ایکس‌ری"

View file

@ -54,6 +54,7 @@
"security" = "Keamanan" "security" = "Keamanan"
"secAlertTitle" = "Peringatan keamanan" "secAlertTitle" = "Peringatan keamanan"
"secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data." "secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data."
"secAlertConf" = "Konfigurasi tertentu telah diidentifikasi rentan terhadap serangan, sehingga mendorong tindakan segera untuk memperkuat protokol keamanan dan melindungi dari potensi pelanggaran keamanan."
[menu] [menu]
"dashboard" = "Ikhtisar" "dashboard" = "Ikhtisar"
@ -304,6 +305,8 @@
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien." "subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
"subURI" = "URI Proxy Terbalik" "subURI" = "URI Proxy Terbalik"
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy." "subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
"fragment" = "Fragmentasi"
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
[pages.xray] [pages.xray]
"title" = "Konfigurasi Xray" "title" = "Konfigurasi Xray"

View file

@ -54,6 +54,7 @@
"security" = "Безопасность" "security" = "Безопасность"
"secAlertTitle" = "Предупреждение системы безопасности" "secAlertTitle" = "Предупреждение системы безопасности"
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных" "secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
"secAlertConf" = "Некоторые конфигурации были определены как уязвимые для атак, что требует немедленных действий по усилению протоколов безопасности и защите от потенциальных нарушений безопасности."
[menu] [menu]
"dashboard" = "Статус системы" "dashboard" = "Статус системы"
@ -304,6 +305,8 @@
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации" "subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
"subURI" = "URI обратного прокси" "subURI" = "URI обратного прокси"
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами" "subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
"fragment" = "Фрагментация"
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
[pages.xray] [pages.xray]
"title" = "Настройки Xray" "title" = "Настройки Xray"

View file

@ -54,6 +54,7 @@
"security" = "Bảo vệ" "security" = "Bảo vệ"
"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7" "secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7"
"secAlertSsl" = "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn" "secAlertSsl" = "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn"
"secAlertConf" = "Một số cấu hình nhất định đã được xác định là dễ bị tấn công, thúc đẩy hành động ngay lập tức để củng cố các giao thức bảo mật và bảo vệ chống lại các vi phạm bảo mật tiềm ẩn."
[menu] [menu]
"dashboard" = "Trạng thái hệ thống" "dashboard" = "Trạng thái hệ thống"
@ -304,6 +305,8 @@
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình" "subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
"subURI" = "URI proxy trung gian" "subURI" = "URI proxy trung gian"
"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian" "subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
"fragment" = "Sự phân mảnh"
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
[pages.xray] [pages.xray]
"title" = "Cài đặt Xray" "title" = "Cài đặt Xray"

View file

@ -54,6 +54,7 @@
"security" = "安全" "security" = "安全"
"secAlertTitle" = "安全警报" "secAlertTitle" = "安全警报"
"secAlertSsl" = "此连接不安全;在激活 TLS 进行数据保护之前,请勿输入敏感信息" "secAlertSsl" = "此连接不安全;在激活 TLS 进行数据保护之前,请勿输入敏感信息"
"secAlertConf" = "某些配置已被确定为容易受到攻击,促使立即采取行动以加强安全协议并防范潜在的安全漏洞。"
[menu] [menu]
"dashboard" = "系统状态" "dashboard" = "系统状态"
@ -304,6 +305,8 @@
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期" "subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
"subURI" = "反向代理 URI" "subURI" = "反向代理 URI"
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用" "subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
"fragment" = "碎片"
"fragmentDesc" = "启用 TLS hello 数据包分段"
[pages.xray] [pages.xray]
"title" = "Xray 设置" "title" = "Xray 设置"

View file

@ -16,7 +16,8 @@ type Config struct {
API json_util.RawMessage `json:"api"` API json_util.RawMessage `json:"api"`
Stats json_util.RawMessage `json:"stats"` Stats json_util.RawMessage `json:"stats"`
Reverse json_util.RawMessage `json:"reverse"` Reverse json_util.RawMessage `json:"reverse"`
FakeDNS json_util.RawMessage `json:"fakeDns"` FakeDNS json_util.RawMessage `json:"fakedns"`
Observatory json_util.RawMessage `json:"observatory"`
} }
func (c *Config) Equals(other *Config) bool { func (c *Config) Equals(other *Config) bool {