[feat] tls and fragment options for extProxy

This commit is contained in:
Alireza Ahmadi 2026-05-15 00:06:25 +02:00
parent c4b20273dd
commit d0784f8ccc
6 changed files with 416 additions and 36 deletions

View file

@ -182,6 +182,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
inbound.Listen = extPrxy["dest"].(string)
inbound.Port = int(extPrxy["port"].(float64))
newStream := stream
var newOutbounds []json_util.RawMessage
switch extPrxy["forceTls"].(string) {
case "tls":
if newStream["security"] != "tls" {
@ -194,9 +195,37 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
delete(newStream, "tlsSettings")
}
}
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
if newStream["security"] != "none" {
newTlsSettings := newStream["tlsSettings"].(map[string]interface{})
if utlsValue, ok := extPrxy["utls"].(string); ok && len(utlsValue) > 0 {
newTlsSettings["fingerprint"] = utlsValue
}
if sniValue, ok := extPrxy["sni"].(string); ok && len(sniValue) > 0 {
newTlsSettings["serverName"] = sniValue
}
if alpnValue, ok := extPrxy["alpn"].([]interface{}); ok && len(alpnValue) > 0 {
newTlsSettings["alpn"] = alpnValue
}
if allowInsecureValue, ok := extPrxy["allowInsecure"].(bool); ok && allowInsecureValue {
newTlsSettings["allowInsecure"] = "1"
}
newStream["tlsSettings"] = newTlsSettings
}
var newOutbounds []json_util.RawMessage
if fragmentValue, ok := extPrxy["fragment"].(map[string]interface{}); ok {
newTag := "freedom_out_" + extPrxy["remark"].(string) + "_" + random.Seq(10)
newOutboundFreedom := map[string]interface{}{
"protocol": "freedom",
"settings": map[string]interface{}{
"fragment": fragmentValue,
},
"tag": newTag,
}
newOutboundFreedomJson, _ := json.MarshalIndent(newOutboundFreedom, "", " ")
newOutbounds = append(newOutbounds, newOutboundFreedomJson)
newStream["sockopt"] = json_util.RawMessage(fmt.Sprintf(`{"dialerProxy": "%s"}`, newTag))
}
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
switch inbound.Protocol {
case "vmess":

View file

@ -295,6 +295,27 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
if newSecurity != "same" {
newObj["tls"] = newSecurity
}
if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 {
newObj["fp"] = utlsValue
}
if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 {
newObj["sni"] = sniValue
}
if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 {
alpn := make([]string, len(alpnValue))
for i, a := range alpnValue {
alpn[i] = a.(string)
}
newObj["alpn"] = strings.Join(alpn, ",")
}
if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue {
newObj["allowInsecure"] = "1"
}
if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok {
newObj["packets"], _ = fragmentValue["packets"].(string)
newObj["length"], _ = fragmentValue["length"].(string)
newObj["interval"], _ = fragmentValue["interval"].(string)
}
if index > 0 {
links += "\n"
}
@ -481,6 +502,27 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 {
params["fp"] = utlsValue
}
if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 {
params["sni"] = sniValue
}
if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 {
alpn := make([]string, len(alpnValue))
for i, a := range alpnValue {
alpn[i] = a.(string)
}
params["alpn"] = strings.Join(alpn, ",")
}
if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue {
params["allowInsecure"] = "1"
}
if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok {
params["packets"] = fragmentValue["packets"].(string)
params["length"] = fragmentValue["length"].(string)
params["interval"] = fragmentValue["interval"].(string)
}
link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
if newSecurity != "same" {
@ -682,6 +724,27 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 {
params["fp"] = utlsValue
}
if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 {
params["sni"] = sniValue
}
if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 {
alpn := make([]string, len(alpnValue))
for i, a := range alpnValue {
alpn[i] = a.(string)
}
params["alpn"] = strings.Join(alpn, ",")
}
if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue {
params["allowInsecure"] = "1"
}
if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok {
params["packets"] = fragmentValue["packets"].(string)
params["length"] = fragmentValue["length"].(string)
params["interval"] = fragmentValue["interval"].(string)
}
link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
if newSecurity != "same" {
@ -859,6 +922,27 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 {
params["fp"] = utlsValue
}
if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 {
params["sni"] = sniValue
}
if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 {
alpn := make([]string, len(alpnValue))
for i, a := range alpnValue {
alpn[i] = a.(string)
}
params["alpn"] = strings.Join(alpn, ",")
}
if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue {
params["allowInsecure"] = "1"
}
if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok {
params["packets"] = fragmentValue["packets"].(string)
params["length"] = fragmentValue["length"].(string)
params["interval"] = fragmentValue["interval"].(string)
}
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port)
if newSecurity != "same" {
@ -956,6 +1040,57 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
protocol = "hysteria"
}
externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 {
params["fp"] = utlsValue
}
if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 {
params["sni"] = sniValue
}
if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 {
alpn := make([]string, len(alpnValue))
for i, a := range alpnValue {
alpn[i] = a.(string)
}
params["alpn"] = strings.Join(alpn, ",")
}
if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue {
params["allowInsecure"] = "1"
}
if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok {
params["packets"] = fragmentValue["packets"].(string)
params["length"] = fragmentValue["length"].(string)
params["interval"] = fragmentValue["interval"].(string)
}
link := fmt.Sprintf("%s://%s@%s:%d", protocol, auth, dest, port)
if newSecurity != "same" {
params["security"] = newSecurity
} else {
params["security"] = "tls"
}
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
q.Add(k, v)
}
url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
link := fmt.Sprintf("%s://%s@%s:%d", protocol, auth, address, port)
url, _ := url.Parse(link)
q := url.Query()

View file

@ -250,6 +250,10 @@ style attribute {
margin-bottom: 2px;
}
.ant-select-selection--multiple {
padding-bottom: 0 !important;
}
@media (min-width: 769px) {
.drawer-handle {
display: none;

View file

@ -1417,6 +1417,66 @@ class FinalMaskStreamSettings extends XrayCommonClass {
}
}
class ExternalProxy extends XrayCommonClass {
constructor(
forceTls = 'same',
dest = '',
port = 443,
remark = '',
fragment = undefined,
utls = '',
sni = '',
alpn = [],
allowInsecure = false,
) {
super();
this.forceTls = forceTls;
this.dest = dest;
this.port = port;
this.remark = remark;
this.fragment = fragment;
this.utls = utls;
this.sni = sni;
this.alpn = alpn;
this.allowInsecure = allowInsecure;
}
get hasFragment() {
return this.fragment !== undefined;
}
set hasFragment(value) {
this.fragment = value ? {packets: 'tlshello', length: '100-200', interval: '10-20'} : undefined;
}
static fromJson(json = {}) {
return new ExternalProxy(
json.forceTls,
json.dest,
json.port,
json.remark,
json.fragment,
json.utls,
json.sni,
json.alpn,
json.allowInsecure,
);
}
toJson() {
return {
forceTls: this.forceTls,
dest: this.dest,
port: this.port,
remark: this.remark,
fragment: this.hasFragment ? this.fragment : undefined,
utls: this.utls,
sni: this.sni,
alpn: this.alpn,
allowInsecure: this.allowInsecure,
};
}
}
class StreamSettings extends XrayCommonClass {
constructor(network = 'tcp',
security = 'none',
@ -1513,7 +1573,7 @@ class StreamSettings extends XrayCommonClass {
return new StreamSettings(
json.network,
json.security,
json.externalProxy,
json.externalProxy ? json.externalProxy.map(ep => ExternalProxy.fromJson(ep)) : [],
TlsStreamSettings.fromJson(json.tlsSettings),
RealityStreamSettings.fromJson(json.realitySettings),
TcpStreamSettings.fromJson(json.tcpSettings),
@ -1533,7 +1593,7 @@ class StreamSettings extends XrayCommonClass {
return {
network: network,
security: this.security,
externalProxy: this.externalProxy,
externalProxy: this.externalProxy.map(ep => ep.toJson()),
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
realitySettings: this.isReality ? this.reality.toJson() : undefined,
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
@ -1796,11 +1856,11 @@ class Inbound extends XrayCommonClass {
this.sniffing = new Sniffing();
}
genVmessLink(address = '', port = this.port, forceTls, remark = '', clientId, security) {
genVmessLink(address = '', port = this.port, extProxy, remark = '', clientId, security) {
if (this.protocol !== Protocols.VMESS) {
return '';
}
const tls = forceTls == 'same' ? this.stream.security : forceTls;
const tls = extProxy.forceTls == 'same' ? this.stream.security : extProxy.forceTls;
let obj = {
v: '2',
ps: remark,
@ -1861,13 +1921,33 @@ class Inbound extends XrayCommonClass {
}
}
if (extProxy) {
if (!ObjectUtil.isEmpty(extProxy.sni)) {
obj.sni = extProxy.sni;
}
if (!ObjectUtil.isEmpty(extProxy.utls)) {
obj.fp = extProxy.utls;
}
if (!ObjectUtil.isArrEmpty(extProxy.alpn)) {
obj.alpn = extProxy.alpn.join(',');
}
if (extProxy.allowInsecure === 'true') {
obj.allowInsecure = true;
}
if (extProxy.fragment) {
obj.packets = extProxy.fragment.packets;
obj.length = extProxy.fragment.length;
obj.interval = extProxy.fragment.interval;
}
}
return 'vmess://' + Base64.encode(JSON.stringify(obj, null, 2));
}
genVLESSLink(address = '', port = this.port, forceTls, remark = '', clientId, flow) {
genVLESSLink(address = '', port = this.port, extProxy, remark = '', clientId, flow) {
const uuid = clientId;
const type = this.stream.network;
const security = forceTls == 'same' ? this.stream.security : forceTls;
const security = extProxy.forceTls == 'same' ? this.stream.security : extProxy.forceTls;
const params = new Map();
params.set("type", this.stream.network);
params.set("encryption", this.settings.encryption);
@ -1961,6 +2041,26 @@ class Inbound extends XrayCommonClass {
params.set("security", "none");
}
if (extProxy) {
if (extProxy.sni?.length > 0) {
params.set("sni", extProxy.sni);
}
if (extProxy.alpn?.length > 0) {
params.set("alpn", extProxy.alpn.join(','));
}
if (extProxy.allowInsecure === 'true') {
params.set("allowInsecure", "1");
}
if (extProxy.utls?.length > 0) {
params.set("fp", extProxy.utls);
}
if (extProxy.fragment) {
params.set("packets", extProxy.fragment.packets);
params.set("length", extProxy.fragment.length);
params.set("interval", extProxy.fragment.interval);
}
}
const link = `vless://${uuid}@${address}:${port}`;
const url = new URL(link);
for (const [key, value] of params) {
@ -1970,10 +2070,10 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
genSSLink(address = '', port = this.port, forceTls, remark = '', clientPassword) {
genSSLink(address = '', port = this.port, extProxy, remark = '', clientPassword) {
let settings = this.settings;
const type = this.stream.network;
const security = forceTls == 'same' ? this.stream.security : forceTls;
const security = extProxy.forceTls == 'same' ? this.stream.security : extProxy.forceTls;
const params = new Map();
params.set("type", this.stream.network);
switch (type) {
@ -2038,6 +2138,26 @@ class Inbound extends XrayCommonClass {
}
}
if (extProxy) {
if (extProxy.sni?.length > 0) {
params.set("sni", extProxy.sni);
}
if (extProxy.alpn?.length > 0) {
params.set("alpn", extProxy.alpn.join(','));
}
if (extProxy.allowInsecure === 'true') {
params.set("allowInsecure", "1");
}
if (extProxy.utls?.length > 0) {
params.set("fp", extProxy.utls);
}
if (extProxy.fragment) {
params.set("packets", extProxy.fragment.packets);
params.set("length", extProxy.fragment.length);
params.set("interval", extProxy.fragment.interval);
}
}
let password = new Array();
if (this.isSS2022) password.push(settings.password);
@ -2052,8 +2172,8 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
genTrojanLink(address = '', port = this.port, forceTls, remark = '', clientPassword) {
const security = forceTls == 'same' ? this.stream.security : forceTls;
genTrojanLink(address = '', port = this.port, extProxy, remark = '', clientPassword) {
const security = extProxy.forceTls == 'same' ? this.stream.security : extProxy.forceTls;
const type = this.stream.network;
const params = new Map();
params.set("type", this.stream.network);
@ -2141,6 +2261,25 @@ class Inbound extends XrayCommonClass {
params.set("security", "none");
}
if (extProxy) {
if (extProxy.sni?.length > 0) {
params.set("sni", extProxy.sni);
}
if (extProxy.alpn?.length > 0) {
params.set("alpn", extProxy.alpn.join(','));
}
if (extProxy.allowInsecure === 'true') {
params.set("allowInsecure", "1");
}
if (extProxy.utls?.length > 0) {
params.set("fp", extProxy.utls);
}
if (extProxy.fragment) {
params.set("packets", extProxy.fragment.packets);
params.set("length", extProxy.fragment.length);
params.set("interval", extProxy.fragment.interval);
}
}
const link = `trojan://${clientPassword}@${address}:${port}`;
const url = new URL(link);
for (const [key, value] of params) {
@ -2150,7 +2289,7 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
genHysteriaLink(address = '', port = this.port, remark = '', clientAuth) {
genHysteriaLink(address = '', port = this.port, extProxy, remark = '', clientAuth) {
const protocol = this.settings.version == 2 ? "hysteria2" : "hysteria";
const link = `${protocol}://${clientAuth}@${address}:${port}`;
@ -2162,6 +2301,25 @@ class Inbound extends XrayCommonClass {
if (this.stream.tls.settings.echConfigList?.length > 0) params.set("ech", this.stream.tls.settings.echConfigList.join(','));
if (this.stream.tls.sni?.length > 0) params.set("sni", this.stream.tls.sni);
if (extProxy) {
if (extProxy.sni?.length > 0) {
params.set("sni", extProxy.sni);
}
if (extProxy.alpn?.length > 0) {
params.set("alpn", extProxy.alpn.join(','));
}
if (extProxy.allowInsecure === 'true') {
params.set("insecure", "1");
}
if (extProxy.utls?.length > 0) {
params.set("fp", extProxy.utls);
}
if (extProxy.fragment) {
params.set("packets", extProxy.fragment.packets);
params.set("length", extProxy.fragment.length);
params.set("interval", extProxy.fragment.interval);
}
}
const url = new URL(link);
for (const [key, value] of params) {
url.searchParams.set(key, value);
@ -2192,18 +2350,19 @@ class Inbound extends XrayCommonClass {
return txt;
}
genLink(address = '', port = this.port, forceTls = 'same', remark = '', client) {
genLink(address = '', port = this.port, extProxy = new ExternalProxy('same'), remark = '', client) {
console.log(extProxy);
switch (this.protocol) {
case Protocols.VMESS:
return this.genVmessLink(address, port, forceTls, remark, client.id, client.security);
return this.genVmessLink(address, port, extProxy, remark, client.id, client.security);
case Protocols.VLESS:
return this.genVLESSLink(address, port, forceTls, remark, client.id, client.flow);
return this.genVLESSLink(address, port, extProxy, remark, client.id, client.flow);
case Protocols.SHADOWSOCKS:
return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : '');
return this.genSSLink(address, port, extProxy, remark, this.isSSMultiUser ? client.password : '');
case Protocols.TROJAN:
return this.genTrojanLink(address, port, forceTls, remark, client.password);
return this.genTrojanLink(address, port, extProxy, remark, client.password);
case Protocols.HYSTERIA:
return this.genHysteriaLink(address, port, remark, client.auth.length > 0 ? client.auth : this.stream.hysteria.auth);
return this.genHysteriaLink(address, port, remark, extProxy, client.auth.length > 0 ? client.auth : this.stream.hysteria.auth);
default: return '';
}
}
@ -2224,7 +2383,7 @@ class Inbound extends XrayCommonClass {
let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar);
result.push({
remark: r,
link: this.genLink(addr, port, 'same', r, client)
link: this.genLink(addr, port, new ExternalProxy('same'), r, client)
});
} else {
this.stream.externalProxy.forEach((ep) => {
@ -2232,7 +2391,7 @@ class Inbound extends XrayCommonClass {
let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar);
result.push({
remark: r,
link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client)
link: this.genLink(ep.dest, ep.port, ep, r, client)
});
});
}

View file

@ -1,24 +1,77 @@
{{define "form/externalProxy"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.stream.common.externalProxy" }}'>
<a-button type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
<a-button type="primary" style="margin-left: 10px" size="small"
@click="inbound.stream.externalProxy.push(new ExternalProxy('same', location.hostname, inbound.port))">+</a-button>
</a-form-item>
<a-input-group style="margin: 5px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
<template>
<a-tooltip title="Force TLS">
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
<template style="margin: 5px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
<a-divider :style="{ margin: '0' }">[[ index + 1 ]]
<a-icon type="delete"
@click="() => inbound.stream.externalProxy.splice(index, 1)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }">
</a-icon>
</a-divider>
<a-input-group compact style="margin-bottom: 10px;">
<a-tooltip title="{{ i18n "security" }}">
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
<a-select-option value="tls">TLS</a-select-option>
</a-select>
</a-tooltip>
</template>
<a-input style="width: 35%; border-radius: 0;" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65535"></a-input-number>
</a-tooltip>
<a-input style="width: 20%; border-radius: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
</a-input-group>
<a-tooltip title="{{ i18n "host" }}">
<a-input v-model.trim="row.dest" style="width: 35%; border-radius: 0;" placeholder='{{ i18n "host" }}'></a-input>
</a-tooltip>
<a-tooltip title="{{ i18n "pages.inbounds.port" }}">
<a-input-number v-model.number="row.port" style="width: 15%;" min="1" max="65535"></a-input-number>
</a-tooltip>
<a-tooltip title="{{ i18n "pages.inbounds.remark" }}">
<a-input v-model.trim="row.remark" style="width: 30%;" placeholder='{{ i18n "pages.inbounds.remark" }}'></a-input>
</a-tooltip>
</a-input-group>
<a-input-group compact v-if="row.forceTls === 'tls' || (row.forceTls === 'same' && inbound.stream.isTls)" style="margin-bottom: 10px;">
<a-tooltip title='{{ i18n "pages.inbounds.stream.tls.uTLS" }}'>
<a-select v-model="row.utls"
style="width: 20%;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-tooltip>
<a-tooltip title='{{ i18n "pages.inbounds.stream.tls.sniDesc" }}'>
<a-input v-model.trim="row.sni" style="width: 35%; border-radius: 0;" placeholder='{{ i18n "pages.inbounds.stream.tls.sni" }}'></a-input>
</a-tooltip>
<a-tooltip title='{{ i18n "pages.inbounds.stream.tls.alpn" }}'>
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="row.alpn" style="width: 30%;">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-tooltip>
<a-tooltip title='{{ i18n "pages.inbounds.stream.tls.allowInsecure" }}'>
<a-select v-model="row.allowInsecure" style="width: 15%;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>False</a-select-option>
<a-select-option value='true'>True</a-select-option>
</a-select>
</a-tooltip>
</a-input-group>
<a-input-group compact style="margin-bottom: 10px;">
<a-tooltip title="{{ i18n "pages.xray.outbound.freedom.fragment" }}">
<a-checkbox v-model="row.hasFragment" style="width: 10%; height: 32px;">{{ i18n "pages.xray.outbound.freedom.fragment" }}</a-checkbox>
</a-tooltip>
<!-- Fragment -->
<template v-if="row.fragment !== undefined">
<a-tooltip title='{{ i18n "pages.xray.outbound.freedom.packets" }}'>
<a-input v-model.trim="row.fragment.packets" placeholder="tlshello" style="width: 30%; border-radius: 1rem 0 0 1rem;" />
</a-tooltip>
<a-tooltip title='{{ i18n "pages.xray.outbound.freedom.length" }}'>
<a-input v-model.trim="row.fragment.length" placeholder="100-200" style="width: 30%; border-radius: 0;" />
</a-tooltip>
<a-tooltip title='{{ i18n "pages.inbounds.stream.finalmask.delay" }}'>
<a-input v-model.trim="row.fragment.interval" placeholder="10-20" style="width: 30%; border-radius: 0 1rem 1rem 0;" />
</a-tooltip>
</template>
</a-input-group>
</template>
</a-form>
{{end}}

View file

@ -398,8 +398,8 @@
[pages.xray.outbound.freedom]
"strategy" = "استراتژی"
"redirect" = "تغییر مسیر"
"fragment" = "Fragment"
"packets" = "Packets"
"fragment" = "فرگمنت"
"packets" = "بسته‌ها"
"length" = "طول"
"interval" = "بازه"
"noises" = "نویزها"