mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-02-28 05:02:59 +00:00
Add configurable host overrides for exported client links
This commit is contained in:
parent
37f0880f8f
commit
cb83e57b2a
28 changed files with 315 additions and 108 deletions
|
|
@ -117,6 +117,7 @@ type Client struct {
|
||||||
Enable bool `json:"enable" form:"enable"` // Whether the client is enabled
|
Enable bool `json:"enable" form:"enable"` // Whether the client is enabled
|
||||||
TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications
|
TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications
|
||||||
SubID string `json:"subId" form:"subId"` // Subscription identifier
|
SubID string `json:"subId" form:"subId"` // Subscription identifier
|
||||||
|
SubHost string `json:"subHost,omitempty"` // Optional host/IP override for exported client links
|
||||||
Comment string `json:"comment" form:"comment"` // Client comment
|
Comment string `json:"comment" form:"comment"` // Client comment
|
||||||
Reset int `json:"reset" form:"reset"` // Reset period in days
|
Reset int `json:"reset" form:"reset"` // Reset period in days
|
||||||
CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp
|
CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
||||||
if err != nil || len(inbounds) == 0 {
|
if err != nil || len(inbounds) == 0 {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
requestHost := strings.TrimSpace(host)
|
||||||
|
defaultClientHost := s.SubService.getDefaultClientHost()
|
||||||
|
|
||||||
var header string
|
var header string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
|
|
@ -103,7 +105,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Enable && client.SubID == subId {
|
if client.Enable && client.SubID == subId {
|
||||||
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
||||||
newConfigs := s.getConfig(inbound, client, host)
|
clientHost := s.SubService.ResolveClientHostWithDefault(inbound, client, requestHost, defaultClientHost)
|
||||||
|
newConfigs := s.getConfig(inbound, client, clientHost)
|
||||||
configArray = append(configArray, newConfigs...)
|
configArray = append(configArray, newConfigs...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,8 @@ import (
|
||||||
|
|
||||||
// SubService provides business logic for generating subscription links and managing subscription data.
|
// SubService provides business logic for generating subscription links and managing subscription data.
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
address string
|
|
||||||
showInfo bool
|
showInfo bool
|
||||||
remarkModel string
|
remarkModel string
|
||||||
datepicker string
|
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +38,8 @@ func NewSubService(showInfo bool, remarkModel string) *SubService {
|
||||||
|
|
||||||
// GetSubs retrieves subscription links for a given subscription ID and host.
|
// GetSubs retrieves subscription links for a given subscription ID and host.
|
||||||
func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) {
|
func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) {
|
||||||
s.address = host
|
requestHost := strings.TrimSpace(host)
|
||||||
|
defaultClientHost := s.getDefaultClientHost()
|
||||||
var result []string
|
var result []string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
var lastOnline int64
|
var lastOnline int64
|
||||||
|
|
@ -54,10 +53,6 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
|
||||||
return nil, 0, traffic, common.NewError("No inbounds found with ", subId)
|
return nil, 0, traffic, common.NewError("No inbounds found with ", subId)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.datepicker, err = s.settingService.GetDatepicker()
|
|
||||||
if err != nil {
|
|
||||||
s.datepicker = "gregorian"
|
|
||||||
}
|
|
||||||
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 {
|
||||||
|
|
@ -76,7 +71,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
|
||||||
}
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Enable && client.SubID == subId {
|
if client.Enable && client.SubID == subId {
|
||||||
link := s.getLink(inbound, client.Email)
|
link := s.getLink(inbound, client.Email, requestHost, defaultClientHost)
|
||||||
result = append(result, link)
|
result = append(result, link)
|
||||||
ct := s.getClientTraffics(inbound.ClientStats, client.Email)
|
ct := s.getClientTraffics(inbound.ClientStats, client.Email)
|
||||||
clientTraffics = append(clientTraffics, ct)
|
clientTraffics = append(clientTraffics, ct)
|
||||||
|
|
@ -161,33 +156,87 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
|
||||||
return inbound.Listen, inbound.Port, string(modifiedStream), nil
|
return inbound.Listen, inbound.Port, string(modifiedStream), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) getDefaultClientHost() string {
|
||||||
|
defaultHost, err := s.settingService.GetSubDefaultHost()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(defaultHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWildcardListen(listen string) bool {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(listen)) {
|
||||||
|
case "", "0.0.0.0", "::", "::0":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getInboundSubHost(inbound *model.Inbound) string {
|
||||||
|
var settings map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
subHost, _ := settings["subHost"].(string)
|
||||||
|
return strings.TrimSpace(subHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) resolveAddress(inbound *model.Inbound, client model.Client, requestHost string, defaultClientHost string) string {
|
||||||
|
if host := strings.TrimSpace(client.SubHost); host != "" {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
if host := s.getInboundSubHost(inbound); host != "" {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
if host := strings.TrimSpace(defaultClientHost); host != "" {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
if !isWildcardListen(inbound.Listen) {
|
||||||
|
return inbound.Listen
|
||||||
|
}
|
||||||
|
return requestHost
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) ResolveClientHost(inbound *model.Inbound, client model.Client, requestHost string) string {
|
||||||
|
return s.ResolveClientHostWithDefault(inbound, client, requestHost, s.getDefaultClientHost())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) ResolveClientHostWithDefault(inbound *model.Inbound, client model.Client, requestHost string, defaultClientHost string) string {
|
||||||
|
host := strings.TrimSpace(requestHost)
|
||||||
|
return s.resolveAddress(inbound, client, host, defaultClientHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findClientByEmail(clients []model.Client, email string) (model.Client, int) {
|
||||||
|
for i, client := range clients {
|
||||||
|
if client.Email == email {
|
||||||
|
return client, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return model.Client{}, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess":
|
case "vmess":
|
||||||
return s.genVmessLink(inbound, email)
|
return s.genVmessLink(inbound, email, requestHost, defaultClientHost)
|
||||||
case "vless":
|
case "vless":
|
||||||
return s.genVlessLink(inbound, email)
|
return s.genVlessLink(inbound, email, requestHost, defaultClientHost)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
return s.genTrojanLink(inbound, email)
|
return s.genTrojanLink(inbound, email, requestHost, defaultClientHost)
|
||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
return s.genShadowsocksLink(inbound, email)
|
return s.genShadowsocksLink(inbound, email, requestHost, defaultClientHost)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genVmessLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||||
if inbound.Protocol != model.VMESS {
|
if inbound.Protocol != model.VMESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var address string
|
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
|
||||||
address = s.address
|
|
||||||
} else {
|
|
||||||
address = inbound.Listen
|
|
||||||
}
|
|
||||||
obj := map[string]any{
|
obj := map[string]any{
|
||||||
"v": "2",
|
"v": "2",
|
||||||
"add": address,
|
"add": "",
|
||||||
"port": inbound.Port,
|
"port": inbound.Port,
|
||||||
"type": "none",
|
"type": "none",
|
||||||
}
|
}
|
||||||
|
|
@ -274,15 +323,13 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
client, clientIndex := findClientByEmail(clients, email)
|
||||||
for i, client := range clients {
|
if clientIndex < 0 {
|
||||||
if client.Email == email {
|
return ""
|
||||||
clientIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
obj["id"] = clients[clientIndex].ID
|
obj["add"] = s.resolveAddress(inbound, client, requestHost, defaultClientHost)
|
||||||
obj["scy"] = clients[clientIndex].Security
|
obj["id"] = client.ID
|
||||||
|
obj["scy"] = client.Security
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]any)
|
externalProxies, _ := stream["externalProxy"].([]any)
|
||||||
|
|
||||||
|
|
@ -319,28 +366,18 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genVlessLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||||
var address string
|
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
|
||||||
address = s.address
|
|
||||||
} else {
|
|
||||||
address = inbound.Listen
|
|
||||||
}
|
|
||||||
|
|
||||||
if inbound.Protocol != model.VLESS {
|
if inbound.Protocol != model.VLESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var stream map[string]any
|
var stream map[string]any
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
client, clientIndex := findClientByEmail(clients, email)
|
||||||
for i, client := range clients {
|
if clientIndex < 0 {
|
||||||
if client.Email == email {
|
return ""
|
||||||
clientIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
uuid := clients[clientIndex].ID
|
uuid := client.ID
|
||||||
port := inbound.Port
|
port := inbound.Port
|
||||||
streamNetwork := stream["network"].(string)
|
streamNetwork := stream["network"].(string)
|
||||||
params := make(map[string]string)
|
params := make(map[string]string)
|
||||||
|
|
@ -430,8 +467,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(client.Flow) > 0 {
|
||||||
params["flow"] = clients[clientIndex].Flow
|
params["flow"] = client.Flow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -464,8 +501,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
params["spx"] = "/" + random.Seq(15)
|
params["spx"] = "/" + random.Seq(15)
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(client.Flow) > 0 {
|
||||||
params["flow"] = clients[clientIndex].Flow
|
params["flow"] = client.Flow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -508,6 +545,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
return strings.Join(links, "\n")
|
return strings.Join(links, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
|
||||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
|
|
@ -523,27 +561,18 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||||
var address string
|
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
|
||||||
address = s.address
|
|
||||||
} else {
|
|
||||||
address = inbound.Listen
|
|
||||||
}
|
|
||||||
if inbound.Protocol != model.Trojan {
|
if inbound.Protocol != model.Trojan {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var stream map[string]any
|
var stream map[string]any
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
client, clientIndex := findClientByEmail(clients, email)
|
||||||
for i, client := range clients {
|
if clientIndex < 0 {
|
||||||
if client.Email == email {
|
return ""
|
||||||
clientIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
password := clients[clientIndex].Password
|
password := client.Password
|
||||||
port := inbound.Port
|
port := inbound.Port
|
||||||
streamNetwork := stream["network"].(string)
|
streamNetwork := stream["network"].(string)
|
||||||
params := make(map[string]string)
|
params := make(map[string]string)
|
||||||
|
|
@ -656,8 +685,8 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
params["spx"] = "/" + random.Seq(15)
|
params["spx"] = "/" + random.Seq(15)
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(client.Flow) > 0 {
|
||||||
params["flow"] = clients[clientIndex].Flow
|
params["flow"] = client.Flow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -703,6 +732,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
return links
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
|
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
|
||||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
||||||
|
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
|
|
@ -719,13 +749,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||||
var address string
|
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
|
||||||
address = s.address
|
|
||||||
} else {
|
|
||||||
address = inbound.Listen
|
|
||||||
}
|
|
||||||
if inbound.Protocol != model.Shadowsocks {
|
if inbound.Protocol != model.Shadowsocks {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -737,12 +761,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
inboundPassword := settings["password"].(string)
|
inboundPassword := settings["password"].(string)
|
||||||
method := settings["method"].(string)
|
method := settings["method"].(string)
|
||||||
clientIndex := -1
|
client, clientIndex := findClientByEmail(clients, email)
|
||||||
for i, client := range clients {
|
if clientIndex < 0 {
|
||||||
if client.Email == email {
|
return ""
|
||||||
clientIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
streamNetwork := stream["network"].(string)
|
streamNetwork := stream["network"].(string)
|
||||||
params := make(map[string]string)
|
params := make(map[string]string)
|
||||||
|
|
@ -827,9 +848,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
encPart := fmt.Sprintf("%s:%s", method, client.Password)
|
||||||
if method[0] == '2' {
|
if method[0] == '2' {
|
||||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, client.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]any)
|
externalProxies, _ := stream["externalProxy"].([]any)
|
||||||
|
|
@ -870,6 +891,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
return links
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
|
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
|
||||||
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
|
|
@ -1161,8 +1183,8 @@ func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray
|
||||||
remained = common.FormatTraffic(left)
|
remained = common.FormatTraffic(left)
|
||||||
}
|
}
|
||||||
|
|
||||||
datepicker := s.datepicker
|
datepicker, err := s.settingService.GetDatepicker()
|
||||||
if datepicker == "" {
|
if err != nil || datepicker == "" {
|
||||||
datepicker = "gregorian"
|
datepicker = "gregorian"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,8 +144,8 @@ class DBInbound {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genInboundLinks(remarkModel) {
|
genInboundLinks(remarkModel, defaultHost = '') {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genInboundLinks(this.remark, remarkModel);
|
return inbound.genInboundLinks(this.remark, remarkModel, defaultHost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1725,10 +1725,25 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genAllLinks(remark = '', remarkModel = '-ieo', client) {
|
resolveLinkHost(client, defaultHost = '') {
|
||||||
|
if (client?.subHost && client.subHost.trim().length > 0) {
|
||||||
|
return client.subHost.trim();
|
||||||
|
}
|
||||||
|
if (this.settings?.subHost && this.settings.subHost.trim().length > 0) {
|
||||||
|
return this.settings.subHost.trim();
|
||||||
|
}
|
||||||
|
if (defaultHost && defaultHost.trim().length > 0) {
|
||||||
|
return defaultHost.trim();
|
||||||
|
}
|
||||||
|
return !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" && this.listen !== "::" && this.listen !== "::0"
|
||||||
|
? this.listen
|
||||||
|
: location.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
genAllLinks(remark = '', remarkModel = '-ieo', client, defaultHost = '') {
|
||||||
let result = [];
|
let result = [];
|
||||||
let email = client ? client.email : '';
|
let email = client ? client.email : '';
|
||||||
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
let addr = this.resolveLinkHost(client, defaultHost);
|
||||||
let port = this.port;
|
let port = this.port;
|
||||||
const separationChar = remarkModel.charAt(0);
|
const separationChar = remarkModel.charAt(0);
|
||||||
const orderChars = remarkModel.slice(1);
|
const orderChars = remarkModel.slice(1);
|
||||||
|
|
@ -1756,12 +1771,12 @@ class Inbound extends XrayCommonClass {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
genInboundLinks(remark = '', remarkModel = '-ieo', defaultHost = '') {
|
||||||
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
let addr = this.resolveLinkHost(null, defaultHost);
|
||||||
if (this.clients) {
|
if (this.clients) {
|
||||||
let links = [];
|
let links = [];
|
||||||
this.clients.forEach((client) => {
|
this.clients.forEach((client) => {
|
||||||
this.genAllLinks(remark, remarkModel, client).forEach(l => {
|
this.genAllLinks(remark, remarkModel, client, defaultHost).forEach(l => {
|
||||||
links.push(l.link);
|
links.push(l.link);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -1811,9 +1826,10 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
Inbound.Settings = class extends XrayCommonClass {
|
Inbound.Settings = class extends XrayCommonClass {
|
||||||
constructor(protocol) {
|
constructor(protocol, subHost = '') {
|
||||||
super();
|
super();
|
||||||
this.protocol = protocol;
|
this.protocol = protocol;
|
||||||
|
this.subHost = subHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getSettings(protocol) {
|
static getSettings(protocol) {
|
||||||
|
|
@ -1877,15 +1893,18 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.VmessSettings(
|
const obj = new Inbound.VmessSettings(
|
||||||
Protocols.VMESS,
|
Protocols.VMESS,
|
||||||
json.clients.map(client => Inbound.VmessSettings.VMESS.fromJson(client)),
|
json.clients.map(client => Inbound.VmessSettings.VMESS.fromJson(client)),
|
||||||
);
|
);
|
||||||
|
obj.subHost = json.subHost || '';
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
||||||
|
subHost: this.subHost || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1901,6 +1920,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||||
enable = true,
|
enable = true,
|
||||||
tgId = '',
|
tgId = '',
|
||||||
subId = RandomUtil.randomLowerAndNum(16),
|
subId = RandomUtil.randomLowerAndNum(16),
|
||||||
|
subHost = '',
|
||||||
comment = '',
|
comment = '',
|
||||||
reset = 0,
|
reset = 0,
|
||||||
created_at = undefined,
|
created_at = undefined,
|
||||||
|
|
@ -1916,6 +1936,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.subHost = subHost;
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
this.reset = reset;
|
this.reset = reset;
|
||||||
this.created_at = created_at;
|
this.created_at = created_at;
|
||||||
|
|
@ -1933,6 +1954,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.subHost,
|
||||||
json.comment,
|
json.comment,
|
||||||
json.reset,
|
json.reset,
|
||||||
json.created_at,
|
json.created_at,
|
||||||
|
|
@ -2009,6 +2031,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||||
json.selectedAuth,
|
json.selectedAuth,
|
||||||
testseed
|
testseed
|
||||||
);
|
);
|
||||||
|
obj.subHost = json.subHost || '';
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2032,6 +2055,9 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||||
if (this.selectedAuth) {
|
if (this.selectedAuth) {
|
||||||
json.selectedAuth = this.selectedAuth;
|
json.selectedAuth = this.selectedAuth;
|
||||||
}
|
}
|
||||||
|
if (this.subHost) {
|
||||||
|
json.subHost = this.subHost;
|
||||||
|
}
|
||||||
|
|
||||||
// Only include testseed if at least one client has a flow set
|
// Only include testseed if at least one client has a flow set
|
||||||
const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== '');
|
const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== '');
|
||||||
|
|
@ -2056,6 +2082,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
enable = true,
|
enable = true,
|
||||||
tgId = '',
|
tgId = '',
|
||||||
subId = RandomUtil.randomLowerAndNum(16),
|
subId = RandomUtil.randomLowerAndNum(16),
|
||||||
|
subHost = '',
|
||||||
comment = '',
|
comment = '',
|
||||||
reset = 0,
|
reset = 0,
|
||||||
created_at = undefined,
|
created_at = undefined,
|
||||||
|
|
@ -2071,6 +2098,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.subHost = subHost;
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
this.reset = reset;
|
this.reset = reset;
|
||||||
this.created_at = created_at;
|
this.created_at = created_at;
|
||||||
|
|
@ -2088,6 +2116,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.subHost,
|
||||||
json.comment,
|
json.comment,
|
||||||
json.reset,
|
json.reset,
|
||||||
json.created_at,
|
json.created_at,
|
||||||
|
|
@ -2177,16 +2206,19 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.TrojanSettings(
|
const obj = new Inbound.TrojanSettings(
|
||||||
Protocols.TROJAN,
|
Protocols.TROJAN,
|
||||||
json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
|
json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
|
||||||
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
|
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
|
||||||
|
obj.subHost = json.subHost || '';
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
clients: Inbound.TrojanSettings.toJsonArray(this.trojans),
|
clients: Inbound.TrojanSettings.toJsonArray(this.trojans),
|
||||||
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks)
|
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks),
|
||||||
|
subHost: this.subHost || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -2201,6 +2233,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
enable = true,
|
enable = true,
|
||||||
tgId = '',
|
tgId = '',
|
||||||
subId = RandomUtil.randomLowerAndNum(16),
|
subId = RandomUtil.randomLowerAndNum(16),
|
||||||
|
subHost = '',
|
||||||
comment = '',
|
comment = '',
|
||||||
reset = 0,
|
reset = 0,
|
||||||
created_at = undefined,
|
created_at = undefined,
|
||||||
|
|
@ -2215,6 +2248,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.subHost = subHost;
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
this.reset = reset;
|
this.reset = reset;
|
||||||
this.created_at = created_at;
|
this.created_at = created_at;
|
||||||
|
|
@ -2231,6 +2265,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
subHost: this.subHost,
|
||||||
comment: this.comment,
|
comment: this.comment,
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
created_at: this.created_at,
|
created_at: this.created_at,
|
||||||
|
|
@ -2248,6 +2283,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.subHost,
|
||||||
json.comment,
|
json.comment,
|
||||||
json.reset,
|
json.reset,
|
||||||
json.created_at,
|
json.created_at,
|
||||||
|
|
@ -2338,7 +2374,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.ShadowsocksSettings(
|
const obj = new Inbound.ShadowsocksSettings(
|
||||||
Protocols.SHADOWSOCKS,
|
Protocols.SHADOWSOCKS,
|
||||||
json.method,
|
json.method,
|
||||||
json.password,
|
json.password,
|
||||||
|
|
@ -2346,6 +2382,8 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
|
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
|
||||||
json.ivCheck,
|
json.ivCheck,
|
||||||
);
|
);
|
||||||
|
obj.subHost = json.subHost || '';
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
|
|
@ -2355,6 +2393,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
network: this.network,
|
network: this.network,
|
||||||
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses),
|
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses),
|
||||||
ivCheck: this.ivCheck,
|
ivCheck: this.ivCheck,
|
||||||
|
subHost: this.subHost || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -2370,6 +2409,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
enable = true,
|
enable = true,
|
||||||
tgId = '',
|
tgId = '',
|
||||||
subId = RandomUtil.randomLowerAndNum(16),
|
subId = RandomUtil.randomLowerAndNum(16),
|
||||||
|
subHost = '',
|
||||||
comment = '',
|
comment = '',
|
||||||
reset = 0,
|
reset = 0,
|
||||||
created_at = undefined,
|
created_at = undefined,
|
||||||
|
|
@ -2385,6 +2425,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.subHost = subHost;
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
this.reset = reset;
|
this.reset = reset;
|
||||||
this.created_at = created_at;
|
this.created_at = created_at;
|
||||||
|
|
@ -2402,6 +2443,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
subHost: this.subHost,
|
||||||
comment: this.comment,
|
comment: this.comment,
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
created_at: this.created_at,
|
created_at: this.created_at,
|
||||||
|
|
@ -2420,6 +2462,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.subHost,
|
||||||
json.comment,
|
json.comment,
|
||||||
json.reset,
|
json.reset,
|
||||||
json.created_at,
|
json.created_at,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ class AllSetting {
|
||||||
this.subPath = "/sub/";
|
this.subPath = "/sub/";
|
||||||
this.subJsonPath = "/json/";
|
this.subJsonPath = "/json/";
|
||||||
this.subDomain = "";
|
this.subDomain = "";
|
||||||
|
this.subDefaultHost = "";
|
||||||
this.externalTrafficInformEnable = false;
|
this.externalTrafficInformEnable = false;
|
||||||
this.externalTrafficInformURI = "";
|
this.externalTrafficInformURI = "";
|
||||||
this.subCertFile = "";
|
this.subCertFile = "";
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ type AllSetting struct {
|
||||||
SubPort int `json:"subPort" form:"subPort"` // Subscription server port
|
SubPort int `json:"subPort" form:"subPort"` // Subscription server port
|
||||||
SubPath string `json:"subPath" form:"subPath"` // Base path for subscription URLs
|
SubPath string `json:"subPath" form:"subPath"` // Base path for subscription URLs
|
||||||
SubDomain string `json:"subDomain" form:"subDomain"` // Domain for subscription server validation
|
SubDomain string `json:"subDomain" form:"subDomain"` // Domain for subscription server validation
|
||||||
|
SubDefaultHost string `json:"subDefaultHost" form:"subDefaultHost"` // Default host/IP used in exported client links
|
||||||
SubCertFile string `json:"subCertFile" form:"subCertFile"` // SSL certificate file for subscription server
|
SubCertFile string `json:"subCertFile" form:"subCertFile"` // SSL certificate file for subscription server
|
||||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` // SSL private key file for subscription server
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` // SSL private key file for subscription server
|
||||||
SubUpdates int `json:"subUpdates" form:"subUpdates"` // Subscription update interval in minutes
|
SubUpdates int `json:"subUpdates" form:"subUpdates"` // Subscription update interval in minutes
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,18 @@
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
<a-input v-model.trim="client.subId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.email && app.subSettings?.enable">
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.settings.subClientHostDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n "pages.settings.subClientHost" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="client.subHost" placeholder="client.example.com"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,19 @@
|
||||||
<a-input-number v-model.number="inbound.port" :min="1"
|
<a-input-number v-model.number="inbound.port" :min="1"
|
||||||
:max="65535"></a-input-number>
|
:max="65535"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
v-if="[Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(inbound.protocol)">
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.settings.subInboundHostDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n "pages.settings.subInboundHost" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="inbound.settings.subHost" placeholder="cdn.example.com"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
|
|
|
||||||
|
|
@ -734,6 +734,7 @@
|
||||||
subURI: '',
|
subURI: '',
|
||||||
subJsonURI: '',
|
subJsonURI: '',
|
||||||
subJsonEnable: false,
|
subJsonEnable: false,
|
||||||
|
defaultHost: '',
|
||||||
},
|
},
|
||||||
remarkModel: '-ieo',
|
remarkModel: '-ieo',
|
||||||
datepicker: 'gregorian',
|
datepicker: 'gregorian',
|
||||||
|
|
@ -791,6 +792,7 @@
|
||||||
subURI: subURI,
|
subURI: subURI,
|
||||||
subJsonURI: subJsonURI,
|
subJsonURI: subJsonURI,
|
||||||
subJsonEnable: subJsonEnable,
|
subJsonEnable: subJsonEnable,
|
||||||
|
defaultHost: subDefaultHost,
|
||||||
};
|
};
|
||||||
this.pageSize = pageSize;
|
this.pageSize = pageSize;
|
||||||
this.remarkModel = remarkModel;
|
this.remarkModel = remarkModel;
|
||||||
|
|
@ -1471,7 +1473,7 @@
|
||||||
inboundLinks(dbInboundId) {
|
inboundLinks(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel), newDbInbound.remark);
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel, this.subSettings.defaultHost), newDbInbound.remark);
|
||||||
},
|
},
|
||||||
exportSubs(dbInboundId) {
|
exportSubs(dbInboundId) {
|
||||||
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
|
@ -1520,7 +1522,7 @@
|
||||||
exportAllLinks() {
|
exportAllLinks() {
|
||||||
let copyText = [];
|
let copyText = [];
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
copyText.push(dbInbound.genInboundLinks(this.remarkModel));
|
copyText.push(dbInbound.genInboundLinks(this.remarkModel, this.subSettings.defaultHost));
|
||||||
}
|
}
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,18 @@
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item v-if="app.subSettings?.enable">
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.settings.subClientHostDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n "pages.settings.subClientHost" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="clientsBulkModal.subHost" placeholder="client.example.com"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item v-if="app.tgBotEnable">
|
<a-form-item v-if="app.tgBotEnable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
|
@ -144,6 +156,7 @@
|
||||||
emailPrefix: "",
|
emailPrefix: "",
|
||||||
emailPostfix: "",
|
emailPostfix: "",
|
||||||
subId: "",
|
subId: "",
|
||||||
|
subHost: "",
|
||||||
tgId: '',
|
tgId: '',
|
||||||
security: "auto",
|
security: "auto",
|
||||||
flow: "",
|
flow: "",
|
||||||
|
|
@ -167,6 +180,7 @@
|
||||||
if (method == 4) newClient.email = "";
|
if (method == 4) newClient.email = "";
|
||||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||||
if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId;
|
if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId;
|
||||||
|
if (clientsBulkModal.subHost.length > 0) newClient.subHost = clientsBulkModal.subHost;
|
||||||
newClient.tgId = clientsBulkModal.tgId;
|
newClient.tgId = clientsBulkModal.tgId;
|
||||||
newClient.security = clientsBulkModal.security;
|
newClient.security = clientsBulkModal.security;
|
||||||
newClient.limitIp = clientsBulkModal.limitIp;
|
newClient.limitIp = clientsBulkModal.limitIp;
|
||||||
|
|
@ -200,6 +214,7 @@
|
||||||
this.emailPrefix = "";
|
this.emailPrefix = "";
|
||||||
this.emailPostfix = "";
|
this.emailPostfix = "";
|
||||||
this.subId = "";
|
this.subId = "";
|
||||||
|
this.subHost = "";
|
||||||
this.tgId = '';
|
this.tgId = '';
|
||||||
this.security = "auto";
|
this.security = "auto";
|
||||||
this.flow = "";
|
this.flow = "";
|
||||||
|
|
|
||||||
|
|
@ -668,9 +668,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||||
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
|
this.links = this.inbound.genInboundLinks(dbInbound.remark, app.remarkModel, app.subSettings.defaultHost).split('\r\n')
|
||||||
} else {
|
} else {
|
||||||
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings, app.subSettings.defaultHost);
|
||||||
}
|
}
|
||||||
if (this.clientSettings) {
|
if (this.clientSettings) {
|
||||||
if (this.clientSettings.subId) {
|
if (this.clientSettings.subId) {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
// Reset the status fetched flag when showing the modal
|
// Reset the status fetched flag when showing the modal
|
||||||
if (qrModalApp) qrModalApp.statusFetched = false;
|
if (qrModalApp) qrModalApp.statusFetched = false;
|
||||||
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||||
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l, index) => {
|
this.inbound.genInboundLinks(dbInbound.remark, app.remarkModel, app.subSettings.defaultHost).split('\r\n').forEach((l, index) => {
|
||||||
this.qrcodes.push({
|
this.qrcodes.push({
|
||||||
remark: "Peer " + (index + 1),
|
remark: "Peer " + (index + 1),
|
||||||
link: l,
|
link: l,
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client, app.subSettings.defaultHost).forEach(l => {
|
||||||
this.qrcodes.push({
|
this.qrcodes.push({
|
||||||
remark: l.remark,
|
remark: l.remark,
|
||||||
link: l.link,
|
link: l.link,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,14 @@
|
||||||
<a-input type="text" v-model="allSetting.subDomain"></a-input>
|
<a-input type="text" v-model="allSetting.subDomain"></a-input>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subDefaultHost"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subDefaultHostDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model.trim="allSetting.subDefaultHost"
|
||||||
|
placeholder="public.example.com"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>{{ i18n "pages.settings.subPort"}}</template>
|
<template #title>{{ i18n "pages.settings.subPort"}}</template>
|
||||||
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
|
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ var defaultValueMap = map[string]string{
|
||||||
"subPort": "2096",
|
"subPort": "2096",
|
||||||
"subPath": "/sub/",
|
"subPath": "/sub/",
|
||||||
"subDomain": "",
|
"subDomain": "",
|
||||||
|
"subDefaultHost": "",
|
||||||
"subCertFile": "",
|
"subCertFile": "",
|
||||||
"subKeyFile": "",
|
"subKeyFile": "",
|
||||||
"subUpdates": "12",
|
"subUpdates": "12",
|
||||||
|
|
@ -515,6 +516,10 @@ func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
return s.getString("subDomain")
|
return s.getString("subDomain")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubDefaultHost() (string, error) {
|
||||||
|
return s.getString("subDefaultHost")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetSubCertFile(subCertFile string) error {
|
func (s *SettingService) SetSubCertFile(subCertFile string) error {
|
||||||
return s.setString("subCertFile", subCertFile)
|
return s.setString("subCertFile", subCertFile)
|
||||||
}
|
}
|
||||||
|
|
@ -754,6 +759,9 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
|
||||||
"subTitle": func() (any, error) { return s.GetSubTitle() },
|
"subTitle": func() (any, error) { return s.GetSubTitle() },
|
||||||
"subURI": func() (any, error) { return s.GetSubURI() },
|
"subURI": func() (any, error) { return s.GetSubURI() },
|
||||||
"subJsonURI": func() (any, error) { return s.GetSubJsonURI() },
|
"subJsonURI": func() (any, error) { return s.GetSubJsonURI() },
|
||||||
|
"subDefaultHost": func() (any, error) {
|
||||||
|
return s.GetSubDefaultHost()
|
||||||
|
},
|
||||||
"remarkModel": func() (any, error) { return s.GetRemarkModel() },
|
"remarkModel": func() (any, error) { return s.GetRemarkModel() },
|
||||||
"datepicker": func() (any, error) { return s.GetDatepicker() },
|
"datepicker": func() (any, error) { return s.GetDatepicker() },
|
||||||
"ipLimitEnable": func() (any, error) { return s.GetIpLimitEnable() },
|
"ipLimitEnable": func() (any, error) { return s.GetIpLimitEnable() },
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "مسار URI لخدمة الاشتراك. (يبدأ بـ '/' وبينتهي بـ '/')"
|
"subPathDesc" = "مسار URI لخدمة الاشتراك. (يبدأ بـ '/' وبينتهي بـ '/')"
|
||||||
"subDomain" = "دومين الاستماع"
|
"subDomain" = "دومين الاستماع"
|
||||||
"subDomainDesc" = "اسم الدومين لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)"
|
"subDomainDesc" = "اسم الدومين لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)"
|
||||||
|
"subDefaultHost" = "مضيف العميل الافتراضي"
|
||||||
|
"subDefaultHostDesc" = "المضيف أو عنوان IP الافتراضي المستخدم في روابط/إعدادات العميل المُصدَّرة."
|
||||||
|
"subInboundHost" = "مضيف الـ Inbound"
|
||||||
|
"subInboundHostDesc" = "تجاوز اختياري للمضيف/IP للروابط المُصدَّرة من هذا الـ Inbound."
|
||||||
|
"subClientHost" = "مضيف العميل"
|
||||||
|
"subClientHostDesc" = "تجاوز اختياري للمضيف/IP للروابط المُصدَّرة لهذا العميل."
|
||||||
"subUpdates" = "فترات التحديث"
|
"subUpdates" = "فترات التحديث"
|
||||||
"subUpdatesDesc" = "فترات تحديث رابط الاشتراك في تطبيقات العملاء. (الوحدة: ساعة)"
|
"subUpdatesDesc" = "فترات تحديث رابط الاشتراك في تطبيقات العملاء. (الوحدة: ساعة)"
|
||||||
"subEncrypt" = "تشفير"
|
"subEncrypt" = "تشفير"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "The URI path for the subscription service. (begins with ‘/‘ and concludes with ‘/‘)"
|
"subPathDesc" = "The URI path for the subscription service. (begins with ‘/‘ and concludes with ‘/‘)"
|
||||||
"subDomain" = "Listen Domain"
|
"subDomain" = "Listen Domain"
|
||||||
"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
|
"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
|
||||||
|
"subDefaultHost" = "Default Client Host"
|
||||||
|
"subDefaultHostDesc" = "Default host or IP used in exported client links/configs."
|
||||||
|
"subInboundHost" = "Inbound Host"
|
||||||
|
"subInboundHostDesc" = "Optional host/IP override for links exported from this inbound."
|
||||||
|
"subClientHost" = "Client Host"
|
||||||
|
"subClientHostDesc" = "Optional host/IP override for links exported for this client."
|
||||||
"subUpdates" = "Update Intervals"
|
"subUpdates" = "Update Intervals"
|
||||||
"subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)"
|
"subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)"
|
||||||
"subEncrypt" = "Encode"
|
"subEncrypt" = "Encode"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "Debe empezar con '/' y terminar con '/'"
|
"subPathDesc" = "Debe empezar con '/' y terminar con '/'"
|
||||||
"subDomain" = "Dominio de Escucha"
|
"subDomain" = "Dominio de Escucha"
|
||||||
"subDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs."
|
"subDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs."
|
||||||
|
"subDefaultHost" = "Host predeterminado del cliente"
|
||||||
|
"subDefaultHostDesc" = "Host o IP predeterminado utilizado en enlaces/configuraciones de cliente exportados."
|
||||||
|
"subInboundHost" = "Host del inbound"
|
||||||
|
"subInboundHostDesc" = "Reemplazo opcional de host/IP para los enlaces exportados desde este inbound."
|
||||||
|
"subClientHost" = "Host del cliente"
|
||||||
|
"subClientHostDesc" = "Reemplazo opcional de host/IP para los enlaces exportados para este cliente."
|
||||||
"subUpdates" = "Intervalos de Actualización de Suscripción"
|
"subUpdates" = "Intervalos de Actualización de Suscripción"
|
||||||
"subUpdatesDesc" = "Horas de intervalo entre actualizaciones en la aplicación del cliente."
|
"subUpdatesDesc" = "Horas de intervalo entre actualizaciones en la aplicación del cliente."
|
||||||
"subEncrypt" = "Encriptar configuraciones"
|
"subEncrypt" = "Encriptar configuraciones"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "برای سرویس سابسکریپشن. با '/' شروع و با '/' خاتمه مییابد URI مسیر"
|
"subPathDesc" = "برای سرویس سابسکریپشن. با '/' شروع و با '/' خاتمه مییابد URI مسیر"
|
||||||
"subDomain" = "نام دامنه"
|
"subDomain" = "نام دامنه"
|
||||||
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنهها و آیپیها خالیبگذارید"
|
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنهها و آیپیها خالیبگذارید"
|
||||||
|
"subDefaultHost" = "هاست پیشفرض کاربر"
|
||||||
|
"subDefaultHostDesc" = "هاست یا IP پیشفرضی که در لینکها/پیکربندیهای خروجی کاربر استفاده میشود."
|
||||||
|
"subInboundHost" = "هاست ورودی"
|
||||||
|
"subInboundHostDesc" = "جایگزینی اختیاری هاست/IP برای لینکهای خروجی این ورودی."
|
||||||
|
"subClientHost" = "هاست کاربر"
|
||||||
|
"subClientHostDesc" = "جایگزینی اختیاری هاست/IP برای لینکهای خروجی این کاربر."
|
||||||
"subUpdates" = "فاصله بروزرسانی سابسکریپشن"
|
"subUpdates" = "فاصله بروزرسانی سابسکریپشن"
|
||||||
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامههای کاربری. (واحد: ساعت"
|
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامههای کاربری. (واحد: ساعت"
|
||||||
"subEncrypt" = "کدگذاری"
|
"subEncrypt" = "کدگذاری"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)"
|
"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)"
|
||||||
"subDomain" = "Domain Pendengar"
|
"subDomain" = "Domain Pendengar"
|
||||||
"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)"
|
"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)"
|
||||||
|
"subDefaultHost" = "Host Klien Default"
|
||||||
|
"subDefaultHostDesc" = "Host atau IP default yang digunakan pada tautan/konfigurasi klien yang diekspor."
|
||||||
|
"subInboundHost" = "Host Inbound"
|
||||||
|
"subInboundHostDesc" = "Override host/IP opsional untuk tautan yang diekspor dari inbound ini."
|
||||||
|
"subClientHost" = "Host Klien"
|
||||||
|
"subClientHostDesc" = "Override host/IP opsional untuk tautan yang diekspor untuk klien ini."
|
||||||
"subUpdates" = "Interval Pembaruan"
|
"subUpdates" = "Interval Pembaruan"
|
||||||
"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)"
|
"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)"
|
||||||
"subEncrypt" = "Encode"
|
"subEncrypt" = "Encode"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "サブスクリプションサービスで使用するURIパス('/'で始まり、'/'で終わる)"
|
"subPathDesc" = "サブスクリプションサービスで使用するURIパス('/'で始まり、'/'で終わる)"
|
||||||
"subDomain" = "監視ドメイン"
|
"subDomain" = "監視ドメイン"
|
||||||
"subDomainDesc" = "サブスクリプションサービスが監視するドメイン(空白にするとすべてのドメインとIPを監視)"
|
"subDomainDesc" = "サブスクリプションサービスが監視するドメイン(空白にするとすべてのドメインとIPを監視)"
|
||||||
|
"subDefaultHost" = "クライアントのデフォルトホスト"
|
||||||
|
"subDefaultHostDesc" = "エクスポートされるクライアントリンク/設定で使用するデフォルトのホストまたはIP。"
|
||||||
|
"subInboundHost" = "インバウンドホスト"
|
||||||
|
"subInboundHostDesc" = "このインバウンドからエクスポートされるリンクに対する任意のホスト/IP上書き。"
|
||||||
|
"subClientHost" = "クライアントホスト"
|
||||||
|
"subClientHostDesc" = "このクライアント向けにエクスポートされるリンクに対する任意のホスト/IP上書き。"
|
||||||
"subUpdates" = "更新間隔"
|
"subUpdates" = "更新間隔"
|
||||||
"subUpdatesDesc" = "クライアントアプリケーションでサブスクリプションURLの更新間隔(単位:時間)"
|
"subUpdatesDesc" = "クライアントアプリケーションでサブスクリプションURLの更新間隔(単位:時間)"
|
||||||
"subEncrypt" = "エンコード"
|
"subEncrypt" = "エンコード"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "O caminho URI para o serviço de assinatura. (começa com ‘/‘ e termina com ‘/‘)"
|
"subPathDesc" = "O caminho URI para o serviço de assinatura. (começa com ‘/‘ e termina com ‘/‘)"
|
||||||
"subDomain" = "Domínio de Escuta"
|
"subDomain" = "Domínio de Escuta"
|
||||||
"subDomainDesc" = "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs)"
|
"subDomainDesc" = "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs)"
|
||||||
|
"subDefaultHost" = "Host padrão do cliente"
|
||||||
|
"subDefaultHostDesc" = "Host ou IP padrão usado nos links/configurações de cliente exportados."
|
||||||
|
"subInboundHost" = "Host do inbound"
|
||||||
|
"subInboundHostDesc" = "Substituição opcional de host/IP para links exportados deste inbound."
|
||||||
|
"subClientHost" = "Host do cliente"
|
||||||
|
"subClientHostDesc" = "Substituição opcional de host/IP para links exportados para este cliente."
|
||||||
"subUpdates" = "Intervalos de Atualização"
|
"subUpdates" = "Intervalos de Atualização"
|
||||||
"subUpdatesDesc" = "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)"
|
"subUpdatesDesc" = "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)"
|
||||||
"subEncrypt" = "Codificar"
|
"subEncrypt" = "Codificar"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
||||||
"subDomain" = "Домен прослушивания"
|
"subDomain" = "Домен прослушивания"
|
||||||
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса"
|
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса"
|
||||||
|
"subDefaultHost" = "Хост клиента по умолчанию"
|
||||||
|
"subDefaultHostDesc" = "Хост или IP по умолчанию, используемый в экспортируемых ссылках/конфигах клиента."
|
||||||
|
"subInboundHost" = "Хост входящего подключения"
|
||||||
|
"subInboundHostDesc" = "Необязательный хост/IP для переопределения в ссылках, экспортируемых из этого входящего подключения."
|
||||||
|
"subClientHost" = "Хост клиента"
|
||||||
|
"subClientHostDesc" = "Необязательный хост/IP для переопределения в ссылках, экспортируемых для этого клиента."
|
||||||
"subUpdates" = "Интервалы обновления подписки"
|
"subUpdates" = "Интервалы обновления подписки"
|
||||||
"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)"
|
"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)"
|
||||||
"subEncrypt" = "Шифровать конфиги"
|
"subEncrypt" = "Шифровать конфиги"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "Abonelik hizmeti için URI yolu. ('/' ile başlar ve '/' ile biter)"
|
"subPathDesc" = "Abonelik hizmeti için URI yolu. ('/' ile başlar ve '/' ile biter)"
|
||||||
"subDomain" = "Dinleme Alan Adı"
|
"subDomain" = "Dinleme Alan Adı"
|
||||||
"subDomainDesc" = "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)"
|
"subDomainDesc" = "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)"
|
||||||
|
"subDefaultHost" = "Varsayılan İstemci Ana Bilgisayarı"
|
||||||
|
"subDefaultHostDesc" = "Dışa aktarılan istemci bağlantıları/yapılandırmalarında kullanılan varsayılan ana bilgisayar veya IP."
|
||||||
|
"subInboundHost" = "Gelen Bağlantı Ana Bilgisayarı"
|
||||||
|
"subInboundHostDesc" = "Bu gelen bağlantıdan dışa aktarılan bağlantılar için isteğe bağlı ana bilgisayar/IP geçersiz kılması."
|
||||||
|
"subClientHost" = "İstemci Ana Bilgisayarı"
|
||||||
|
"subClientHostDesc" = "Bu istemci için dışa aktarılan bağlantılar için isteğe bağlı ana bilgisayar/IP geçersiz kılması."
|
||||||
"subUpdates" = "Güncelleme Aralıkları"
|
"subUpdates" = "Güncelleme Aralıkları"
|
||||||
"subUpdatesDesc" = "Müşteri uygulamalarındaki abonelik URL'sinin güncelleme aralıkları. (birim: saat)"
|
"subUpdatesDesc" = "Müşteri uygulamalarındaki abonelik URL'sinin güncelleme aralıkları. (birim: saat)"
|
||||||
"subEncrypt" = "Şifrele"
|
"subEncrypt" = "Şifrele"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "Шлях URI для служби підписки. (починається з ‘/‘ і закінчується ‘/‘)"
|
"subPathDesc" = "Шлях URI для служби підписки. (починається з ‘/‘ і закінчується ‘/‘)"
|
||||||
"subDomain" = "Домен прослуховування"
|
"subDomain" = "Домен прослуховування"
|
||||||
"subDomainDesc" = "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)"
|
"subDomainDesc" = "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)"
|
||||||
|
"subDefaultHost" = "Хост клієнта за замовчуванням"
|
||||||
|
"subDefaultHostDesc" = "Хост або IP за замовчуванням, що використовується в експортованих посиланнях/конфігах клієнта."
|
||||||
|
"subInboundHost" = "Хост вхідного підключення"
|
||||||
|
"subInboundHostDesc" = "Необов'язкове перевизначення host/IP для посилань, експортованих із цього вхідного підключення."
|
||||||
|
"subClientHost" = "Хост клієнта"
|
||||||
|
"subClientHostDesc" = "Необов'язкове перевизначення host/IP для посилань, експортованих для цього клієнта."
|
||||||
"subUpdates" = "Інтервали оновлення"
|
"subUpdates" = "Інтервали оновлення"
|
||||||
"subUpdatesDesc" = "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)"
|
"subUpdatesDesc" = "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)"
|
||||||
"subEncrypt" = "Закодувати"
|
"subEncrypt" = "Закодувати"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
|
"subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
|
||||||
"subDomain" = "Tên miền con"
|
"subDomain" = "Tên miền con"
|
||||||
"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
|
"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
|
||||||
|
"subDefaultHost" = "Host mặc định của khách hàng"
|
||||||
|
"subDefaultHostDesc" = "Host hoặc IP mặc định được dùng trong liên kết/cấu hình khách hàng đã xuất."
|
||||||
|
"subInboundHost" = "Host inbound"
|
||||||
|
"subInboundHostDesc" = "Ghi đè host/IP tùy chọn cho các liên kết được xuất từ inbound này."
|
||||||
|
"subClientHost" = "Host khách hàng"
|
||||||
|
"subClientHostDesc" = "Ghi đè host/IP tùy chọn cho các liên kết được xuất cho khách hàng này."
|
||||||
"subUpdates" = "Khoảng thời gian cập nhật gói đăng ký"
|
"subUpdates" = "Khoảng thời gian cập nhật gói đăng ký"
|
||||||
"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách"
|
"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách"
|
||||||
"subEncrypt" = "Mã hóa cấu hình"
|
"subEncrypt" = "Mã hóa cấu hình"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)"
|
"subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)"
|
||||||
"subDomain" = "监听域名"
|
"subDomain" = "监听域名"
|
||||||
"subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP)"
|
"subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP)"
|
||||||
|
"subDefaultHost" = "默认客户端主机"
|
||||||
|
"subDefaultHostDesc" = "用于导出客户端链接/配置的默认主机或 IP。"
|
||||||
|
"subInboundHost" = "入站主机"
|
||||||
|
"subInboundHostDesc" = "为从该入站导出的链接提供可选的主机/IP 覆盖。"
|
||||||
|
"subClientHost" = "客户端主机"
|
||||||
|
"subClientHostDesc" = "为该客户端导出的链接提供可选的主机/IP 覆盖。"
|
||||||
"subUpdates" = "更新间隔"
|
"subUpdates" = "更新间隔"
|
||||||
"subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)"
|
"subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)"
|
||||||
"subEncrypt" = "编码"
|
"subEncrypt" = "编码"
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,12 @@
|
||||||
"subPathDesc" = "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)"
|
"subPathDesc" = "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)"
|
||||||
"subDomain" = "監聽域名"
|
"subDomain" = "監聽域名"
|
||||||
"subDomainDesc" = "訂閱服務監聽的域名(留空表示監聽所有域名和 IP)"
|
"subDomainDesc" = "訂閱服務監聽的域名(留空表示監聽所有域名和 IP)"
|
||||||
|
"subDefaultHost" = "預設客戶端主機"
|
||||||
|
"subDefaultHostDesc" = "用於匯出客戶端連結/設定的預設主機或 IP。"
|
||||||
|
"subInboundHost" = "入站主機"
|
||||||
|
"subInboundHostDesc" = "可選的主機/IP 覆寫,用於從此入站匯出的連結。"
|
||||||
|
"subClientHost" = "客戶端主機"
|
||||||
|
"subClientHostDesc" = "可選的主機/IP 覆寫,用於為此客戶端匯出的連結。"
|
||||||
"subUpdates" = "更新間隔"
|
"subUpdates" = "更新間隔"
|
||||||
"subUpdatesDesc" = "客戶端應用中訂閱 URL 的更新間隔(單位:小時)"
|
"subUpdatesDesc" = "客戶端應用中訂閱 URL 的更新間隔(單位:小時)"
|
||||||
"subEncrypt" = "編碼"
|
"subEncrypt" = "編碼"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue