mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-11-29 02:42:51 +00:00
update
This commit is contained in:
parent
784ed39930
commit
fd9a62a91b
12 changed files with 275 additions and 3 deletions
|
|
@ -53,6 +53,7 @@ type Inbound struct {
|
||||||
StreamSettings string `json:"streamSettings" form:"streamSettings"`
|
StreamSettings string `json:"streamSettings" form:"streamSettings"`
|
||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
|
VlessDomain string `json:"vlessDomain" form:"vlessDomain"` // Domain for VLESS protocol in subscriptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutboundTraffics tracks traffic statistics for Xray outbound connections.
|
// OutboundTraffics tracks traffic statistics for Xray outbound connections.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ package sub
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -50,9 +52,8 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
|
||||||
return nil, 0, traffic, err
|
return nil, 0, traffic, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(inbounds) == 0 {
|
// Allow empty inbounds if we have remote servers configured
|
||||||
return nil, 0, traffic, common.NewError("No inbounds found with ", subId)
|
hasLocalInbounds := len(inbounds) > 0
|
||||||
}
|
|
||||||
|
|
||||||
s.datepicker, err = s.settingService.GetDatepicker()
|
s.datepicker, err = s.settingService.GetDatepicker()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -109,9 +110,139 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get subscriptions from remote servers
|
||||||
|
remoteSubs, err := s.getRemoteSubscriptions(subId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to get remote subscriptions:", err)
|
||||||
|
} else {
|
||||||
|
result = append(result, remoteSubs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no local inbounds and no remote subscriptions, return error
|
||||||
|
if !hasLocalInbounds && len(remoteSubs) == 0 {
|
||||||
|
return nil, 0, traffic, common.NewError("No inbounds found with ", subId)
|
||||||
|
}
|
||||||
|
|
||||||
return result, lastOnline, traffic, nil
|
return result, lastOnline, traffic, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRemoteSubscriptions fetches subscription links from configured remote servers.
|
||||||
|
func (s *SubService) getRemoteSubscriptions(subId string) ([]string, error) {
|
||||||
|
remoteServers, err := s.settingService.GetSubRemoteServers()
|
||||||
|
if err != nil || remoteServers == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverURLs []string
|
||||||
|
if err := json.Unmarshal([]byte(remoteServers), &serverURLs); err != nil {
|
||||||
|
logger.Warning("Failed to parse remote servers JSON:", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(serverURLs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var allSubs []string
|
||||||
|
subPath, err := s.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
subPath = "/sub/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure subPath ends with /
|
||||||
|
if !strings.HasSuffix(subPath, "/") {
|
||||||
|
subPath += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, serverURL := range serverURLs {
|
||||||
|
if serverURL == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse URL to check if it contains a path
|
||||||
|
parsedURL, err := url.Parse(serverURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("Failed to parse server URL %s: %v", serverURL, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var subURL string
|
||||||
|
// If URL already has a path, use it and append subId
|
||||||
|
if parsedURL.Path != "" && parsedURL.Path != "/" {
|
||||||
|
// URL has a path, use it as base and append subId
|
||||||
|
basePath := strings.TrimRight(parsedURL.Path, "/")
|
||||||
|
// Preserve query and fragment if present
|
||||||
|
subURL = fmt.Sprintf("%s://%s%s/%s", parsedURL.Scheme, parsedURL.Host, basePath, subId)
|
||||||
|
if parsedURL.RawQuery != "" {
|
||||||
|
subURL += "?" + parsedURL.RawQuery
|
||||||
|
}
|
||||||
|
if parsedURL.Fragment != "" {
|
||||||
|
subURL += "#" + parsedURL.Fragment
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No path specified, use default subPath
|
||||||
|
serverURL = strings.TrimRight(serverURL, "/")
|
||||||
|
subURL = serverURL + subPath + subId
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", subURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("Failed to create request for %s: %v", subURL, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set User-Agent to identify as subscription client
|
||||||
|
req.Header.Set("User-Agent", "3x-ui-subscription-client/1.0")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("Failed to fetch subscription from %s: %v", subURL, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
logger.Warningf("Remote server %s returned status %d", subURL, resp.StatusCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("Failed to read response from %s: %v", subURL, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if response is base64 encoded (check Subscription-Userinfo header)
|
||||||
|
content := string(body)
|
||||||
|
if resp.Header.Get("Subscription-Userinfo") != "" {
|
||||||
|
// Try to decode base64
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(content)
|
||||||
|
if err == nil {
|
||||||
|
content = string(decoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by newlines and add non-empty lines
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" && (strings.HasPrefix(line, "vless://") ||
|
||||||
|
strings.HasPrefix(line, "vmess://") ||
|
||||||
|
strings.HasPrefix(line, "trojan://") ||
|
||||||
|
strings.HasPrefix(line, "ss://")) {
|
||||||
|
allSubs = append(allSubs, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allSubs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
|
|
@ -318,6 +449,12 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
|
|
||||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
address := s.address
|
address := s.address
|
||||||
|
// Priority: 1) inbound-specific domain, 2) global VLESS domain setting, 3) default address
|
||||||
|
if inbound.VlessDomain != "" {
|
||||||
|
address = inbound.VlessDomain
|
||||||
|
} else if vlessDomain, err := s.settingService.GetSubVlessDomain(); err == nil && vlessDomain != "" {
|
||||||
|
address = vlessDomain
|
||||||
|
}
|
||||||
if inbound.Protocol != model.VLESS {
|
if inbound.Protocol != model.VLESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class DBInbound {
|
||||||
this.streamSettings = "";
|
this.streamSettings = "";
|
||||||
this.tag = "";
|
this.tag = "";
|
||||||
this.sniffing = "";
|
this.sniffing = "";
|
||||||
|
this.vlessDomain = "";
|
||||||
this.clientStats = ""
|
this.clientStats = ""
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ class AllSetting {
|
||||||
this.subPath = "/sub/";
|
this.subPath = "/sub/";
|
||||||
this.subJsonPath = "/json/";
|
this.subJsonPath = "/json/";
|
||||||
this.subDomain = "";
|
this.subDomain = "";
|
||||||
|
this.subVlessDomain = "";
|
||||||
this.externalTrafficInformEnable = false;
|
this.externalTrafficInformEnable = false;
|
||||||
this.externalTrafficInformURI = "";
|
this.externalTrafficInformURI = "";
|
||||||
this.subCertFile = "";
|
this.subCertFile = "";
|
||||||
|
|
@ -47,6 +48,7 @@ class AllSetting {
|
||||||
this.subJsonNoises = "";
|
this.subJsonNoises = "";
|
||||||
this.subJsonMux = "";
|
this.subJsonMux = "";
|
||||||
this.subJsonRules = "";
|
this.subJsonRules = "";
|
||||||
|
this.subRemoteServers = "";
|
||||||
|
|
||||||
this.timeLocation = "Local";
|
this.timeLocation = "Local";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,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
|
||||||
|
SubVlessDomain string `json:"subVlessDomain" form:"subVlessDomain"` // Domain for VLESS protocol in subscriptions
|
||||||
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
|
||||||
|
|
@ -75,6 +76,7 @@ type AllSetting struct {
|
||||||
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration
|
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration
|
||||||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration
|
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration
|
||||||
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||||
|
SubRemoteServers string `json:"subRemoteServers" form:"subRemoteServers"` // JSON array of remote server URLs for subscription aggregation
|
||||||
|
|
||||||
// LDAP settings
|
// LDAP settings
|
||||||
LdapEnable bool `json:"ldapEnable" form:"ldapEnable"`
|
LdapEnable bool `json:"ldapEnable" form:"ldapEnable"`
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,20 @@
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.vlessDomainDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n "pages.inbounds.vlessDomain" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="dbInbound.vlessDomain" placeholder='{{ i18n "pages.inbounds.vlessDomainPlaceholder" }}'></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
<template v-if="!inbound.stream.isTLS || !inbound.stream.isReality">
|
<template v-if="!inbound.stream.isTLS || !inbound.stream.isReality">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Authentication">
|
<a-form-item label="Authentication">
|
||||||
|
|
|
||||||
|
|
@ -1076,6 +1076,7 @@
|
||||||
port: inbound.port,
|
port: inbound.port,
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
|
vlessDomain: dbInbound.vlessDomain || "",
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) {
|
if (inbound.canEnableStream()) {
|
||||||
data.streamSettings = inbound.stream.toString();
|
data.streamSettings = inbound.stream.toString();
|
||||||
|
|
@ -1101,6 +1102,7 @@
|
||||||
port: inbound.port,
|
port: inbound.port,
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
|
vlessDomain: dbInbound.vlessDomain || "",
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) {
|
if (inbound.canEnableStream()) {
|
||||||
data.streamSettings = inbound.stream.toString();
|
data.streamSettings = inbound.stream.toString();
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@
|
||||||
user: {},
|
user: {},
|
||||||
lang: LanguageManager.getLanguage(),
|
lang: LanguageManager.getLanguage(),
|
||||||
inboundOptions: [],
|
inboundOptions: [],
|
||||||
|
subRemoteServersInput: '',
|
||||||
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
|
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
|
||||||
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
||||||
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
|
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
|
||||||
|
|
@ -243,10 +244,61 @@
|
||||||
|
|
||||||
this.oldAllSetting = new AllSetting(msg.obj);
|
this.oldAllSetting = new AllSetting(msg.obj);
|
||||||
this.allSetting = new AllSetting(msg.obj);
|
this.allSetting = new AllSetting(msg.obj);
|
||||||
|
// Initialize subRemoteServersInput from JSON
|
||||||
|
this.updateSubRemoteServersInput();
|
||||||
app.changeRemarkSample();
|
app.changeRemarkSample();
|
||||||
this.saveBtnDisable = true;
|
this.saveBtnDisable = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateSubRemoteServersInput() {
|
||||||
|
const remoteServers = this.allSetting.subRemoteServers;
|
||||||
|
if (!remoteServers || typeof remoteServers !== 'string' || remoteServers.trim() === '') {
|
||||||
|
this.subRemoteServersInput = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const servers = JSON.parse(remoteServers);
|
||||||
|
if (Array.isArray(servers) && servers.length > 0) {
|
||||||
|
this.subRemoteServersInput = servers.join(', ');
|
||||||
|
} else {
|
||||||
|
this.subRemoteServersInput = '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// If not valid JSON, treat as comma-separated string (backward compatibility)
|
||||||
|
this.subRemoteServersInput = remoteServers;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
convertRemoteServersToJson() {
|
||||||
|
const input = this.subRemoteServersInput;
|
||||||
|
if (!input || input.trim() === '') {
|
||||||
|
this.allSetting.subRemoteServers = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by comma (handle both ", " and ","), filter empty strings, trim each
|
||||||
|
const servers = input
|
||||||
|
.split(/\s*,\s*/)
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(s => s.length > 0)
|
||||||
|
.map(server => {
|
||||||
|
server = server.trim();
|
||||||
|
// Only add https:// if no protocol specified
|
||||||
|
if (!server.startsWith('http://') && !server.startsWith('https://')) {
|
||||||
|
server = 'https://' + server;
|
||||||
|
}
|
||||||
|
return server;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to JSON array
|
||||||
|
try {
|
||||||
|
this.allSetting.subRemoteServers = JSON.stringify(servers);
|
||||||
|
console.log('Converted remote servers to JSON:', this.allSetting.subRemoteServers);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to convert remote servers to JSON:', e);
|
||||||
|
this.allSetting.subRemoteServers = JSON.stringify([]);
|
||||||
|
}
|
||||||
|
},
|
||||||
async loadInboundTags() {
|
async loadInboundTags() {
|
||||||
const msg = await HttpUtil.get("/panel/api/inbounds/list");
|
const msg = await HttpUtil.get("/panel/api/inbounds/list");
|
||||||
if (msg && msg.success && Array.isArray(msg.obj)) {
|
if (msg && msg.success && Array.isArray(msg.obj)) {
|
||||||
|
|
@ -259,6 +311,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async updateAllSetting() {
|
async updateAllSetting() {
|
||||||
|
// Convert remote servers input to JSON before saving
|
||||||
|
this.convertRemoteServersToJson();
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
|
const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
|
|
@ -567,6 +621,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'allSetting.subRemoteServers': {
|
||||||
|
handler(newVal, oldVal) {
|
||||||
|
// Only update if value actually changed
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
this.updateSubRemoteServersInput();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getAllSetting();
|
await this.getAllSetting();
|
||||||
await this.loadInboundTags();
|
await this.loadInboundTags();
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,13 @@
|
||||||
<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.subVlessDomain"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subVlessDomainDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.subVlessDomain"></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>
|
||||||
|
|
@ -62,6 +69,15 @@
|
||||||
v-model="allSetting.subURI"></a-input>
|
v-model="allSetting.subURI"></a-input>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subRemoteServers"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subRemoteServersDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" placeholder='server1.com, http://serv2.com/config/sub, https://server3.com'
|
||||||
|
v-model="subRemoteServersInput"
|
||||||
|
@blur="convertRemoteServersToJson"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel key="2" header='{{ i18n "pages.settings.information" }}'>
|
<a-collapse-panel key="2" header='{{ i18n "pages.settings.information" }}'>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ var defaultValueMap = map[string]string{
|
||||||
"subPort": "2096",
|
"subPort": "2096",
|
||||||
"subPath": "/sub/",
|
"subPath": "/sub/",
|
||||||
"subDomain": "",
|
"subDomain": "",
|
||||||
|
"subVlessDomain": "",
|
||||||
"subCertFile": "",
|
"subCertFile": "",
|
||||||
"subKeyFile": "",
|
"subKeyFile": "",
|
||||||
"subUpdates": "12",
|
"subUpdates": "12",
|
||||||
|
|
@ -69,6 +70,7 @@ var defaultValueMap = map[string]string{
|
||||||
"subJsonNoises": "",
|
"subJsonNoises": "",
|
||||||
"subJsonMux": "",
|
"subJsonMux": "",
|
||||||
"subJsonRules": "",
|
"subJsonRules": "",
|
||||||
|
"subRemoteServers": "",
|
||||||
"datepicker": "gregorian",
|
"datepicker": "gregorian",
|
||||||
"warp": "",
|
"warp": "",
|
||||||
"externalTrafficInformEnable": "false",
|
"externalTrafficInformEnable": "false",
|
||||||
|
|
@ -479,6 +481,14 @@ func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
return s.getString("subDomain")
|
return s.getString("subDomain")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubVlessDomain() (string, error) {
|
||||||
|
return s.getString("subVlessDomain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetSubVlessDomain(subVlessDomain string) error {
|
||||||
|
return s.setString("subVlessDomain", subVlessDomain)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetSubCertFile(subCertFile string) error {
|
func (s *SettingService) SetSubCertFile(subCertFile string) error {
|
||||||
return s.setString("subCertFile", subCertFile)
|
return s.setString("subCertFile", subCertFile)
|
||||||
}
|
}
|
||||||
|
|
@ -535,6 +545,14 @@ func (s *SettingService) GetSubJsonRules() (string, error) {
|
||||||
return s.getString("subJsonRules")
|
return s.getString("subJsonRules")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubRemoteServers() (string, error) {
|
||||||
|
return s.getString("subRemoteServers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetSubRemoteServers(servers string) error {
|
||||||
|
return s.setString("subRemoteServers", servers)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetDatepicker() (string, error) {
|
func (s *SettingService) GetDatepicker() (string, error) {
|
||||||
return s.getString("datepicker")
|
return s.getString("datepicker")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,9 @@
|
||||||
"privatekey" = "Private Key"
|
"privatekey" = "Private Key"
|
||||||
"clickOnQRcode" = "Click on QR Code to Copy"
|
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||||
"client" = "Client"
|
"client" = "Client"
|
||||||
|
"vlessDomain" = "VLESS Domain"
|
||||||
|
"vlessDomainDesc" = "Domain name to use in VLESS subscription links for this inbound. (leave blank to use global setting or default)"
|
||||||
|
"vlessDomainPlaceholder" = "e.g., vless.example.com"
|
||||||
"export" = "Export All URLs"
|
"export" = "Export All URLs"
|
||||||
"clone" = "Clone"
|
"clone" = "Clone"
|
||||||
"cloneInbound" = "Clone"
|
"cloneInbound" = "Clone"
|
||||||
|
|
@ -386,6 +389,8 @@
|
||||||
"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)"
|
||||||
|
"subVlessDomain" = "VLESS Domain"
|
||||||
|
"subVlessDomainDesc" = "Domain name to use in VLESS subscription links. (leave blank to use the default subscription domain)"
|
||||||
"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"
|
||||||
|
|
@ -394,6 +399,8 @@
|
||||||
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
|
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
|
||||||
"subURI" = "Reverse Proxy URI"
|
"subURI" = "Reverse Proxy URI"
|
||||||
"subURIDesc" = "The URI path of the subscription URL for use behind proxies."
|
"subURIDesc" = "The URI path of the subscription URL for use behind proxies."
|
||||||
|
"subRemoteServers" = "Remote Subscription Servers"
|
||||||
|
"subRemoteServersDesc" = "Comma-separated list of remote server URLs. Can be full paths (e.g., http://serv1.com/config/sub) or domains (e.g., server1.com). SubId will be appended automatically."
|
||||||
"externalTrafficInformEnable" = "External Traffic Inform"
|
"externalTrafficInformEnable" = "External Traffic Inform"
|
||||||
"externalTrafficInformEnableDesc" = "Inform external API on every traffic update."
|
"externalTrafficInformEnableDesc" = "Inform external API on every traffic update."
|
||||||
"externalTrafficInformURI" = "External Traffic Inform URI"
|
"externalTrafficInformURI" = "External Traffic Inform URI"
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,9 @@
|
||||||
"privatekey" = "Приватный ключ"
|
"privatekey" = "Приватный ключ"
|
||||||
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
||||||
"client" = "Клиент"
|
"client" = "Клиент"
|
||||||
|
"vlessDomain" = "Домен для VLESS"
|
||||||
|
"vlessDomainDesc" = "Домен для использования в ссылках подписки VLESS для этого inbound. (оставьте пустым для использования глобальной настройки или значения по умолчанию)"
|
||||||
|
"vlessDomainPlaceholder" = "например, vless.example.com"
|
||||||
"export" = "Экспорт ссылок"
|
"export" = "Экспорт ссылок"
|
||||||
"clone" = "Клонировать"
|
"clone" = "Клонировать"
|
||||||
"cloneInbound" = "Клонировать"
|
"cloneInbound" = "Клонировать"
|
||||||
|
|
@ -386,6 +389,8 @@
|
||||||
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
||||||
"subDomain" = "Домен прослушивания"
|
"subDomain" = "Домен прослушивания"
|
||||||
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса"
|
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса"
|
||||||
|
"subVlessDomain" = "Домен для VLESS"
|
||||||
|
"subVlessDomainDesc" = "Домен для использования в ссылках подписки VLESS. (оставьте пустым для использования домена подписки по умолчанию)"
|
||||||
"subUpdates" = "Интервалы обновления подписки"
|
"subUpdates" = "Интервалы обновления подписки"
|
||||||
"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)"
|
"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)"
|
||||||
"subEncrypt" = "Шифровать конфиги"
|
"subEncrypt" = "Шифровать конфиги"
|
||||||
|
|
@ -394,6 +399,8 @@
|
||||||
"subShowInfoDesc" = "Отображать остаток трафика и дату окончания после имени конфигурации"
|
"subShowInfoDesc" = "Отображать остаток трафика и дату окончания после имени конфигурации"
|
||||||
"subURI" = "URI обратного прокси"
|
"subURI" = "URI обратного прокси"
|
||||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||||
|
"subRemoteServers" = "Удаленные серверы подписок"
|
||||||
|
"subRemoteServersDesc" = "Список URL-ов удаленных серверов через запятую. Можно указать полный путь (например, http://serv1.com/config/sub) или домен (например, server1.com). SubId будет добавлен автоматически."
|
||||||
"externalTrafficInformEnable" = "Информация о внешнем трафике"
|
"externalTrafficInformEnable" = "Информация о внешнем трафике"
|
||||||
"externalTrafficInformEnableDesc" = "Информировать внешний API о каждом обновлении трафика"
|
"externalTrafficInformEnableDesc" = "Информировать внешний API о каждом обновлении трафика"
|
||||||
"externalTrafficInformURI" = "URI информации о внешнем трафике"
|
"externalTrafficInformURI" = "URI информации о внешнем трафике"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue