mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-22 00:35:48 +00:00
[sub] json + fragment
This commit is contained in:
105
sub/default.json
Normal file
105
sub/default.json
Normal 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": {}
|
||||||
|
}
|
||||||
44
sub/sub.go
44
sub/sub.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
359
sub/subJsonService.go
Normal file
359
sub/subJsonService.go
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
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...)
|
||||||
|
var outboundStrings []json_util.RawMessage
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
outboundStrings = append(outboundStrings, outbound)
|
||||||
|
}
|
||||||
|
configJson["outbounds"] = outboundStrings
|
||||||
|
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"`
|
||||||
|
}
|
||||||
@@ -18,50 +18,46 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
address string
|
address string
|
||||||
showInfo bool
|
showInfo bool
|
||||||
remarkModel string
|
remarkModel string
|
||||||
|
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare Inbounds
|
||||||
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 {
|
||||||
@@ -72,6 +68,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
|
||||||
@@ -93,11 +91,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) {
|
||||||
@@ -126,7 +121,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{}).
|
||||||
@@ -134,9 +129,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 {
|
||||||
|
|||||||
@@ -24,6 +24,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 = "";
|
||||||
@@ -31,6 +32,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";
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
@@ -103,6 +106,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)
|
||||||
|
|||||||
@@ -5,10 +5,16 @@
|
|||||||
width="300px" :class="themeSwitcher.currentTheme">
|
width="300px" :class="themeSwitcher.currentTheme">
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
||||||
<template v-if="app.subSettings.enable && qrModal.subId">
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
<a-divider>Subscription</a-divider>
|
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
|
||||||
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
|
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
|
||||||
id="qrCode-sub"
|
id="qrCode-sub"
|
||||||
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
|
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">
|
||||||
@@ -83,12 +89,16 @@
|
|||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
return app.subSettings.subURI+subID+'?name='+subID;
|
return app.subSettings.subURI+subID+'?name='+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);
|
||||||
|
|||||||
@@ -162,7 +162,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)">
|
||||||
@@ -171,6 +171,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;">
|
||||||
|
<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>
|
||||||
@@ -341,6 +351,7 @@
|
|||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
subLink: '',
|
subLink: '',
|
||||||
|
subJsonLink: '',
|
||||||
tgLink: '',
|
tgLink: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
@@ -357,6 +368,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);
|
||||||
}
|
}
|
||||||
if (this.clientSettings.tgId) {
|
if (this.clientSettings.tgId) {
|
||||||
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
|
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
|
||||||
@@ -369,6 +381,9 @@
|
|||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
return app.subSettings.subURI+subID+'?name='+subID;
|
return app.subSettings.subURI+subID+'?name='+subID;
|
||||||
|
},
|
||||||
|
genSubJsonLink(subID) {
|
||||||
|
return app.subSettings.subJsonURI+subID+'?name='+subID;;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -564,7 +564,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',
|
||||||
tgBotEnable: false,
|
tgBotEnable: false,
|
||||||
@@ -609,7 +610,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;
|
||||||
@@ -646,8 +648,12 @@
|
|||||||
clientCount = clients.length;
|
clientCount = clients.length;
|
||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
if (client.enable) {
|
||||||
if(this.isClientOnline(client.email)) online.push(client.email);
|
active.push(client.email);
|
||||||
|
if(this.isClientOnline(client.email)) online.push(client.email);
|
||||||
|
} else {
|
||||||
|
deactive.push(client.email);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if (!client.enable) {
|
if (!client.enable) {
|
||||||
|
|||||||
@@ -211,6 +211,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>
|
||||||
@@ -240,6 +251,24 @@
|
|||||||
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
||||||
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
||||||
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('') : [];
|
||||||
@@ -321,6 +350,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
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: {
|
confAlerts: {
|
||||||
get: function() {
|
get: function() {
|
||||||
if (!this.allSetting) return [];
|
if (!this.allSetting) return [];
|
||||||
@@ -330,6 +385,8 @@
|
|||||||
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "pages.settings.panelConfig"}} {{ i18n "pages.settings.panelUrlPath"}}');
|
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "pages.settings.panelConfig"}} {{ i18n "pages.settings.panelUrlPath"}}');
|
||||||
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
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"}}');
|
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
|
return alerts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ var defaultValueMap = map[string]string{
|
|||||||
"subEncrypt": "true",
|
"subEncrypt": "true",
|
||||||
"subShowInfo": "false",
|
"subShowInfo": "false",
|
||||||
"subURI": "",
|
"subURI": "",
|
||||||
|
"subJsonPath": "/json/",
|
||||||
|
"subJsonURI": "",
|
||||||
|
"subJsonFragment": "",
|
||||||
"warp": "",
|
"warp": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,17 +356,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
|
|
||||||
}
|
func (s *SettingService) GetSubJsonPath() (string, error) {
|
||||||
if !strings.HasPrefix(subPath, "/") {
|
return s.getString("subJsonPath")
|
||||||
subPath = "/" + subPath
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(subPath, "/") {
|
|
||||||
subPath += "/"
|
|
||||||
}
|
|
||||||
return subPath, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubDomain() (string, error) {
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
@@ -378,8 +375,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) {
|
||||||
@@ -398,6 +395,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) GetWarp() (string, error) {
|
func (s *SettingService) GetWarp() (string, error) {
|
||||||
return s.getString("warp")
|
return s.getString("warp")
|
||||||
}
|
}
|
||||||
@@ -446,6 +451,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() },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,10 +465,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()
|
||||||
@@ -483,12 +490,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
|
||||||
|
|||||||
@@ -294,6 +294,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 subscription service will use the URI that has been set up behind reverse proxies."
|
"subURIDesc" = "The subscription service will use the URI that has been set up behind reverse proxies."
|
||||||
|
"fragment" = "Fragmentation"
|
||||||
|
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "Modify Settings"
|
"modifySettings" = "Modify Settings"
|
||||||
|
|||||||
@@ -293,6 +293,8 @@
|
|||||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
||||||
"subURI" = "پراکسی معکوس URI"
|
"subURI" = "پراکسی معکوس URI"
|
||||||
"subURIDesc" = "سابسکریپشن از لینکی که در پشت پراکسیهای معکوس تنظیم شده، استفاده خواهدکرد"
|
"subURIDesc" = "سابسکریپشن از لینکی که در پشت پراکسیهای معکوس تنظیم شده، استفاده خواهدکرد"
|
||||||
|
"fragment" = "تکهتکه شدن"
|
||||||
|
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تیالاس"
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "ویرایش تنظیمات"
|
"modifySettings" = "ویرایش تنظیمات"
|
||||||
|
|||||||
@@ -294,6 +294,8 @@
|
|||||||
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||||
"subURI" = "URI обратного прокси"
|
"subURI" = "URI обратного прокси"
|
||||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||||
|
"fragment" = "Фрагментация"
|
||||||
|
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "Изменение настроек"
|
"modifySettings" = "Изменение настроек"
|
||||||
|
|||||||
@@ -294,6 +294,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 ngược"
|
"subURI" = "URI proxy ngược"
|
||||||
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy"
|
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy"
|
||||||
|
"fragment" = "Sự phân mảnh"
|
||||||
|
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "Sửa đổi cài đặt"
|
"modifySettings" = "Sửa đổi cài đặt"
|
||||||
|
|||||||
@@ -294,6 +294,8 @@
|
|||||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||||
"subURI" = "反向代理 URI"
|
"subURI" = "反向代理 URI"
|
||||||
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
||||||
|
"fragment" = "碎片"
|
||||||
|
"fragmentDesc" = "启用 TLS hello 数据包分段"
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "修改设置"
|
"modifySettings" = "修改设置"
|
||||||
|
|||||||
Reference in New Issue
Block a user