finalmask

Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
This commit is contained in:
MHSanaei 2026-04-20 16:38:33 +02:00
parent ae5ad505d0
commit 04b4fb4384
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
24 changed files with 1341 additions and 1226 deletions

View file

@ -579,6 +579,8 @@ class UdpMask extends CommonClass {
case 'header-dns': case 'header-dns':
case 'xdns': case 'xdns':
return { domain: settings.domain || '' }; return { domain: settings.domain || '' };
case 'xicmp':
return { ip: settings.ip || '', id: settings.id ?? 0 };
case 'mkcp-original': case 'mkcp-original':
case 'header-dtls': case 'header-dtls':
case 'header-srtp': case 'header-srtp':
@ -586,6 +588,12 @@ class UdpMask extends CommonClass {
case 'header-wechat': case 'header-wechat':
case 'header-wireguard': case 'header-wireguard':
return {}; // No settings needed return {}; // No settings needed
case 'header-custom':
return { client: [], server: [] };
case 'noise':
return { reset: 0, noise: [] };
case 'sudoku':
return { ascii: '', customTable: '', customTables: [], paddingMin: 0, paddingMax: 0 };
default: default:
return settings; return settings;
} }

View file

@ -1,6 +1,5 @@
{{define "form/client"}} {{define "form/client"}}
<a-form layout="horizontal" v-if="client" :colon="false" <a-form layout="horizontal" v-if="client" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'> <a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
<a-switch v-model="client.enable"></a-switch> <a-switch v-model="client.enable"></a-switch>
</a-form-item> </a-form-item>
@ -11,14 +10,12 @@
<span>{{ i18n "pages.inbounds.emailDesc" }}</span> <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template> </template>
{{ i18n "pages.inbounds.email" }} {{ i18n "pages.inbounds.email" }}
<a-icon type="sync" <a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
@click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="client.email"></a-input> <a-input v-model.trim="client.email"></a-input>
</a-form-item> </a-form-item>
<a-form-item <a-form-item v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
@ -28,8 +25,7 @@
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS"
@click="client.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)" @click="client.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)"
type="sync"></a-icon> type="sync"></a-icon>
<a-icon v-if="inbound.protocol === Protocols.TROJAN" <a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)"
@click="client.password = RandomUtil.randomSeq(10)"
type="sync"> </a-icon> type="sync"> </a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
@ -42,29 +38,24 @@
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
Auth Password Auth Password
<a-icon @click="client.auth = RandomUtil.randomSeq(10)" <a-icon @click="client.auth = RandomUtil.randomSeq(10)" type="sync"></a-icon>
type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="client.auth"></a-input> <a-input v-model.trim="client.auth"></a-input>
</a-form-item> </a-form-item>
<a-form-item <a-form-item v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
ID <a-icon @click="client.id = RandomUtil.randomUUID()" ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon>
type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="client.id"></a-input> <a-input v-model.trim="client.id"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.protocol === Protocols.VMESS" <a-form-item v-if="inbound.protocol === Protocols.VMESS" label='{{ i18n "security" }}'>
label='{{ i18n "security" }}'> <a-select v-model="client.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select v-model="client.security"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key <a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
@ -76,8 +67,7 @@
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span> <span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</template> </template>
Subscription Subscription
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="client.subId"></a-input> <a-input v-model.trim="client.subId"></a-input>
@ -92,8 +82,7 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number :style="{ width: '50%' }" v-model.number="client.tgId" <a-input-number :style="{ width: '50%' }" v-model.number="client.tgId" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item v-if="client.email" label='{{ i18n "comment" }}'> <a-form-item v-if="client.email" label='{{ i18n "comment" }}'>
<a-input v-model.trim="client.comment"></a-input> <a-input v-model.trim="client.comment"></a-input>
@ -108,11 +97,9 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number v-model.number="client.limitIp" <a-input-number v-model.number="client.limitIp" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item <a-form-item v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit">
v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit">
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
@ -127,21 +114,17 @@
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span> <span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
</template> </template>
<span :style="{ color: '#FF4D4F' }"> <span :style="{ color: '#FF4D4F' }">
<a-icon type="delete" <a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
@click="clearDBClientIps(client.email)"></a-icon>
</span> </span>
</a-tooltip> </a-tooltip>
<a-form layout="block"> <a-form layout="block">
<a-textarea id="clientIPs" readonly <a-textarea id="clientIPs" readonly @click="getDBClientIps(client.email)" placeholder="Click To Get IPs"
@click="getDBClientIps(client.email)"
placeholder="Click To Get IPs"
:auto-size="{ minRows: 5, maxRows: 10 }"> :auto-size="{ minRows: 5, maxRows: 10 }">
</a-textarea> </a-textarea>
</a-form> </a-form>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'> <a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'>
<a-select v-model="client.flow" <a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value selected>{{ i18n "none" }}</a-select-option> <a-select-option value selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key
]]</a-select-option> ]]</a-select-option>
@ -157,12 +140,10 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number v-model.number="client._totalGB" <a-input-number v-model.number="client._totalGB" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'> <a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
<a-tag <a-tag :color="ColorUtils.clientUsageColor(clientStats, app.trafficDiff)">
:color="ColorUtils.clientUsageColor(clientStats, app.trafficDiff)">
[[ SizeFormatter.sizeFormat(clientStats.up) ]] / [[ SizeFormatter.sizeFormat(clientStats.up) ]] /
[[ SizeFormatter.sizeFormat(clientStats.down) ]] [[ SizeFormatter.sizeFormat(clientStats.down) ]]
([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]]) ([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]])
@ -170,19 +151,15 @@
<a-tooltip> <a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" <template slot="title">{{ i18n "pages.inbounds.resetTraffic"
}}</template> }}</template>
<a-icon type="retweet" <a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
@click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
v-if="client.email.length > 0"></a-icon> v-if="client.email.length > 0"></a-icon>
</a-tooltip> </a-tooltip>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'> <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="delayedStart" <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
@click="client._expiryTime=0"></a-switch>
</a-form-item> </a-form-item>
<a-form-item v-if="delayedStart" <a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
label='{{ i18n "pages.client.expireDays" }}'> <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
<a-input-number v-model.number="delayedExpireDays"
:min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item v-else> <a-form-item v-else>
<template slot="label"> <template slot="label">
@ -193,14 +170,10 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-date-picker v-if="datepicker == 'gregorian'" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
:dropdown-class-name="themeSwitcher.currentTheme" <a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
v-model="client._expiryTime"></a-date-picker> value="client._expiryTime" v-model="client._expiryTime"></a-persian-datepicker>
<a-persian-datepicker v-else
placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="client._expiryTime"
v-model="client._expiryTime"></a-persian-datepicker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag> <a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item> </a-form-item>
<a-form-item v-if="client.expiryTime != 0"> <a-form-item v-if="client.expiryTime != 0">

View file

@ -1,7 +1,6 @@
{{define "form/inbound"}} {{define "form/inbound"}}
<!-- base --> <!-- base -->
<a-form :colon="false" :label-col="{ md: {span:8} }" <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "enable" }}'> <a-form-item label='{{ i18n "enable" }}'>
<a-switch v-model="dbInbound.enable"></a-switch> <a-switch v-model="dbInbound.enable"></a-switch>
</a-form-item> </a-form-item>
@ -10,8 +9,7 @@
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "protocol" }}'> <a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="inbound.protocol" :disabled="isEdit" <a-select v-model="inbound.protocol" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p <a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
@ -31,8 +29,7 @@
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> <a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<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> <a-form-item>
@ -45,8 +42,7 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number v-model.number="dbInbound.totalGB" <a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
@ -55,10 +51,8 @@
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.periodicTrafficResetDesc" <span>{{ i18n "pages.inbounds.periodicTrafficResetDesc"
}}</span> }}</span>
<br <br v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0"> <span v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
<span
v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
<strong>{{ i18n "pages.inbounds.lastReset" }}:</strong> <strong>{{ i18n "pages.inbounds.lastReset" }}:</strong>
<span>[[ <span>[[
IntlUtil.formatDate(dbInbound.lastTrafficResetTime) IntlUtil.formatDate(dbInbound.lastTrafficResetTime)
@ -69,8 +63,7 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-select v-model="dbInbound.trafficReset" <a-select v-model="dbInbound.trafficReset" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="never">{{ i18n <a-select-option value="never">{{ i18n
"pages.inbounds.periodicTrafficReset.never" }}</a-select-option> "pages.inbounds.periodicTrafficReset.never" }}</a-select-option>
<a-select-option value="hourly">{{ i18n <a-select-option value="hourly">{{ i18n
@ -98,13 +91,10 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-date-picker :style="{ width: '100%' }" <a-date-picker :style="{ width: '100%' }" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime"></a-date-picker> v-model="dbInbound._expiryTime"></a-date-picker>
<a-persian-datepicker v-else <a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"> value="dbInbound._expiryTime" v-model="dbInbound._expiryTime">
</a-persian-datepicker> </a-persian-datepicker>
</a-form-item> </a-form-item>

View file

@ -1,14 +1,11 @@
{{define "form/outbound"}} {{define "form/outbound"}}
<!-- base --> <!-- base -->
<a-tabs :active-key="outModal.activeKey" <a-tabs :active-key="outModal.activeKey" :style="{ padding: '0', backgroundColor: 'transparent' }"
:style="{ padding: '0', backgroundColor: 'transparent' }"
@change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }"> @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
<a-tab-pane key="1" tab="Form"> <a-tab-pane key="1" tab="Form">
<a-form :colon="false" :label-col="{ md: {span:8} }" <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "protocol" }}'> <a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="outbound.protocol" <a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x,y in Protocols" :value="x">[[ y <a-select-option v-for="x,y in Protocols" :value="x">[[ y
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
@ -25,8 +22,7 @@
<!-- freedom settings--> <!-- freedom settings-->
<template v-if="outbound.protocol === Protocols.Freedom"> <template v-if="outbound.protocol === Protocols.Freedom">
<a-form-item label='Strategy'> <a-form-item label='Strategy'>
<a-select v-model="outbound.settings.domainStrategy" <a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ <a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[
s ]]</a-select-option> s ]]</a-select-option>
</a-select> </a-select>
@ -41,8 +37,7 @@
</a-form-item> </a-form-item>
<template v-if="Object.keys(outbound.settings.fragment).length >0"> <template v-if="Object.keys(outbound.settings.fragment).length >0">
<a-form-item label='Packets'> <a-form-item label='Packets'>
<a-select v-model="outbound.settings.fragment.packets" <a-select v-model="outbound.settings.fragment.packets" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s <a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
@ -51,12 +46,10 @@
<a-input v-model.trim="outbound.settings.fragment.length"></a-input> <a-input v-model.trim="outbound.settings.fragment.length"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Interval'> <a-form-item label='Interval'>
<a-input <a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Max Split'> <a-form-item label='Max Split'>
<a-input <a-input v-model.trim="outbound.settings.fragment.maxSplit"></a-input>
v-model.trim="outbound.settings.fragment.maxSplit"></a-input>
</a-form-item> </a-form-item>
</template> </template>
@ -70,13 +63,11 @@
<!-- Add Noise Button --> <!-- Add Noise Button -->
<template v-if="outbound.settings.noises.length > 0"> <template v-if="outbound.settings.noises.length > 0">
<a-form-item label="Noises"> <a-form-item label="Noises">
<a-button icon="plus" type="primary" size="small" <a-button icon="plus" type="primary" size="small" @click="outbound.settings.addNoise()"></a-button>
@click="outbound.settings.addNoise()"></a-button>
</a-form-item> </a-form-item>
<!-- Noise Configurations --> <!-- Noise Configurations -->
<a-form v-for="(noise, index) in outbound.settings.noises" <a-form v-for="(noise, index) in outbound.settings.noises" :key="index" :colon="false"
:key="index" :colon="false"
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> Noise [[ index + 1 ]] <a-divider :style="{ margin: '0' }"> Noise [[ index + 1 ]]
<a-icon v-if="outbound.settings.noises.length > 1" type="delete" <a-icon v-if="outbound.settings.noises.length > 1" type="delete"
@ -84,10 +75,8 @@
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon> :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider> </a-divider>
<a-form-item label='Type'> <a-form-item label='Type'>
<a-select v-model="noise.type" <a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option v-for="s in ['rand','base64','str', 'hex']" :value="s">[[ s ]]</a-select-option>
<a-select-option v-for="s in ['rand','base64','str', 'hex']"
:value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Packet'> <a-form-item label='Packet'>
@ -97,8 +86,7 @@
<a-input v-model.trim="noise.delay"></a-input> <a-input v-model.trim="noise.delay"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Apply To'> <a-form-item label='Apply To'>
<a-select v-model="noise.applyTo" <a-select v-model="noise.applyTo" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['ip','ipv4','ipv6']" :value="s">[[ <a-select-option v-for="s in ['ip','ipv4','ipv6']" :value="s">[[
s ]]</a-select-option> s ]]</a-select-option>
</a-select> </a-select>
@ -110,8 +98,7 @@
<!-- blackhole settings --> <!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole"> <template v-if="outbound.protocol === Protocols.Blackhole">
<a-form-item label='Response Type'> <a-form-item label='Response Type'>
<a-select v-model="outbound.settings.type" <a-select v-model="outbound.settings.type" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s <a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
@ -121,21 +108,18 @@
<!-- dns settings --> <!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS"> <template v-if="outbound.protocol === Protocols.DNS">
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> <a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select v-model="outbound.settings.network" <a-select v-model="outbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s <a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='non-IP queries'> <a-form-item label='non-IP queries'>
<a-select v-model="outbound.settings.nonIPQuery" <a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['reject','drop','skip']" :value="s">[[ <a-select-option v-for="s in ['reject','drop','skip']" :value="s">[[
s ]]</a-select-option> s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" <a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types'>
label='Block Types'>
<a-input v-model.number="outbound.settings.blockTypes"></a-input> <a-input v-model.number="outbound.settings.blockTypes"></a-input>
</a-form-item> </a-form-item>
</template> </template>
@ -172,19 +156,15 @@
<a-input disabled v-model="outbound.settings.pubKey"></a-input> <a-input disabled v-model="outbound.settings.pubKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'> <a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'>
<a-select v-model="outbound.settings.domainStrategy" <a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option v-for="wds in ['', ...WireguardDomainStrategy]" :value="wds">[[ wds ]]</a-select-option>
<a-select-option v-for="wds in ['', ...WireguardDomainStrategy]"
:value="wds">[[ wds ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='MTU'> <a-form-item label='MTU'>
<a-input-number v-model.number="outbound.settings.mtu" <a-input-number v-model.number="outbound.settings.mtu" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Workers'> <a-form-item label='Workers'>
<a-input-number v-model.number="outbound.settings.workers" <a-input-number v-model.number="outbound.settings.workers" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='No Kernel Tun'> <a-form-item label='No Kernel Tun'>
<a-switch v-model="outbound.settings.noKernelTun"></a-switch> <a-switch v-model="outbound.settings.noKernelTun"></a-switch>
@ -200,14 +180,11 @@
<a-input v-model="outbound.settings.reserved"></a-input> <a-input v-model="outbound.settings.reserved"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Peers"> <a-form-item label="Peers">
<a-button icon="plus" type="primary" size="small" <a-button icon="plus" type="primary" size="small" @click="outbound.settings.addPeer()"></a-button>
@click="outbound.settings.addPeer()"></a-button>
</a-form-item> </a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" <a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }"
:label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }"> :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> Peer [[ index + 1 ]] <a-icon <a-divider :style="{ margin: '0' }"> Peer [[ index + 1 ]] <a-icon v-if="outbound.settings.peers.length>1"
v-if="outbound.settings.peers.length>1"
type="delete" @click="() => outbound.settings.delPeer(index)" type="delete" @click="() => outbound.settings.delPeer(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon> :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider> </a-divider>
@ -223,21 +200,17 @@
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} {{ i18n "pages.xray.wireguard.allowedIPs" }}
<a-button icon="plus" type="primary" size="small" <a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
@click="peer.allowedIPs.push('')"></a-button>
</template> </template>
<template v-for="(aip, index) in peer.allowedIPs" <template v-for="(aip, index) in peer.allowedIPs" :style="{ marginBottom: '10px' }">
:style="{ marginBottom: '10px' }">
<a-input v-model.trim="peer.allowedIPs[index]"> <a-input v-model.trim="peer.allowedIPs[index]">
<a-button icon="minus" v-if="peer.allowedIPs.length>1" <a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small"
slot="addonAfter" size="small"
@click="peer.allowedIPs.splice(index, 1)"></a-button> @click="peer.allowedIPs.splice(index, 1)"></a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item label='Keep Alive'> <a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" <a-input-number v-model.number="peer.keepAlive" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
</a-form> </a-form>
</template> </template>
@ -248,14 +221,12 @@
<a-input v-model.trim="outbound.settings.address"></a-input> <a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> <a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="outbound.settings.port" :min="1" <a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
:max="65532"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
<!-- VLESS/VMess user settings --> <!-- VLESS/VMess user settings -->
<template <template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
<a-form-item label='ID'> <a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input> <a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item> </a-form-item>
@ -263,8 +234,7 @@
<!-- vmess settings --> <!-- vmess settings -->
<template v-if="outbound.protocol === Protocols.VMess"> <template v-if="outbound.protocol === Protocols.VMess">
<a-form-item label='Security'> <a-form-item label='Security'>
<a-select v-model="outbound.settings.security" <a-select v-model="outbound.settings.security" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key <a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
@ -279,8 +249,7 @@
</template> </template>
<template v-if="outbound.canEnableTlsFlow()"> <template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'> <a-form-item label='Flow'>
<a-select v-model="outbound.settings.flow" <a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value selected>{{ i18n "none" <a-select-option value selected>{{ i18n "none"
}}</a-select-option> }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[
@ -291,35 +260,26 @@
<!-- XTLS Vision Advanced Settings --> <!-- XTLS Vision Advanced Settings -->
<template v-if="outbound.canEnableVisionSeed()"> <template v-if="outbound.canEnableVisionSeed()">
<a-form-item label="Vision Pre-Connect"> <a-form-item label="Vision Pre-Connect">
<a-input-number v-model.number="outbound.settings.testpre" :min="0" <a-input-number v-model.number="outbound.settings.testpre" :min="0" :max="10" :style="{ width: '100%' }"
:max="10" :style="{ width: '100%' }"
placeholder="0"></a-input-number> placeholder="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="Vision Seed"> <a-form-item label="Vision Seed">
<a-row :gutter="8"> <a-row :gutter="8">
<a-col :span="6"> <a-col :span="6">
<a-input-number v-model.number="outbound.settings.testseed[0]" <a-input-number v-model.number="outbound.settings.testseed[0]" :min="0" :max="9999"
:min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
:style="{ width: '100%' }" placeholder="900"
addon-before="[0]"></a-input-number>
</a-col> </a-col>
<a-col :span="6"> <a-col :span="6">
<a-input-number v-model.number="outbound.settings.testseed[1]" <a-input-number v-model.number="outbound.settings.testseed[1]" :min="0" :max="9999"
:min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
:style="{ width: '100%' }" placeholder="500"
addon-before="[1]"></a-input-number>
</a-col> </a-col>
<a-col :span="6"> <a-col :span="6">
<a-input-number v-model.number="outbound.settings.testseed[2]" <a-input-number v-model.number="outbound.settings.testseed[2]" :min="0" :max="9999"
:min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
:style="{ width: '100%' }" placeholder="900"
addon-before="[2]"></a-input-number>
</a-col> </a-col>
<a-col :span="6"> <a-col :span="6">
<a-input-number v-model.number="outbound.settings.testseed[3]" <a-input-number v-model.number="outbound.settings.testseed[3]" :min="0" :max="9999"
:min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
:style="{ width: '100%' }" placeholder="256"
addon-before="[3]"></a-input-number>
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
@ -339,8 +299,7 @@
</template> </template>
<!-- trojan/shadowsocks --> <!-- trojan/shadowsocks -->
<template <template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.password"></a-input> <a-input v-model.trim="outbound.settings.password"></a-input>
</a-form-item> </a-form-item>
@ -349,10 +308,8 @@
<!-- shadowsocks --> <!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks"> <template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'> <a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="outbound.settings.method" <a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option v-for="(method, method_name) in SSMethods" :value="method">[[ method_name
<a-select-option v-for="(method, method_name) in SSMethods"
:value="method">[[ method_name
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -360,8 +317,7 @@
<a-switch v-model="outbound.settings.uot"></a-switch> <a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='UoTVersion'> <a-form-item label='UoTVersion'>
<a-input-number v-model.number="outbound.settings.UoTVersion" <a-input-number v-model.number="outbound.settings.UoTVersion" :min="1" :max="2"></a-input-number>
:min="1" :max="2"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
@ -369,16 +325,14 @@
<!-- hysteria settings --> <!-- hysteria settings -->
<template v-if="outbound.protocol === Protocols.Hysteria"> <template v-if="outbound.protocol === Protocols.Hysteria">
<a-form-item label='Version'> <a-form-item label='Version'>
<a-input-number v-model.number="outbound.settings.version" :min="2" <a-input-number v-model.number="outbound.settings.version" :min="2" :max="2" disabled></a-input-number>
:max="2" disabled></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
<!-- stream settings --> <!-- stream settings -->
<template v-if="outbound.canEnableStream()"> <template v-if="outbound.canEnableStream()">
<a-form-item label='{{ i18n "transmission" }}'> <a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="outbound.stream.network" <a-select v-model="outbound.stream.network" @change="streamNetworkChange"
@change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP (RAW)</a-select-option> <a-select-option value="tcp">TCP (RAW)</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option> <a-select-option value="kcp">mKCP</a-select-option>
@ -408,31 +362,25 @@
<!-- kcp --> <!-- kcp -->
<template v-if="outbound.stream.network === 'kcp'"> <template v-if="outbound.stream.network === 'kcp'">
<a-form-item label='MTU'> <a-form-item label='MTU'>
<a-input-number v-model.number="outbound.stream.kcp.mtu" <a-input-number v-model.number="outbound.stream.kcp.mtu" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='TTI (ms)'> <a-form-item label='TTI (ms)'>
<a-input-number v-model.number="outbound.stream.kcp.tti" <a-input-number v-model.number="outbound.stream.kcp.tti" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Uplink (MB/s)'> <a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.upCap" <a-input-number v-model.number="outbound.stream.kcp.upCap" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Downlink (MB/s)'> <a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.downCap" <a-input-number v-model.number="outbound.stream.kcp.downCap" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Congestion'> <a-form-item label='Congestion'>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch> <a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='Read Buffer (MB)'> <a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer" <a-input-number v-model.number="outbound.stream.kcp.readBuffer" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Write Buffer (MB)'> <a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer" <a-input-number v-model.number="outbound.stream.kcp.writeBuffer" min="0"></a-input-number>
min="0"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
@ -445,8 +393,7 @@
<a-input v-model.trim="outbound.stream.ws.path"></a-input> <a-input v-model.trim="outbound.stream.ws.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Heartbeat Period'> <a-form-item label='Heartbeat Period'>
<a-input-number v-model.number="outbound.stream.ws.heartbeatPeriod" <a-input-number v-model.number="outbound.stream.ws.heartbeatPeriod" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
@ -482,8 +429,7 @@
<a-input v-model.trim="outbound.stream.xhttp.path"></a-input> <a-input v-model.trim="outbound.stream.xhttp.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Mode'> <a-form-item label='Mode'>
<a-select v-model="outbound.stream.xhttp.mode" <a-select v-model="outbound.stream.xhttp.mode" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key <a-select-option v-for="key in MODE_OPTION" :value="key">[[ key
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
@ -492,36 +438,26 @@
v-if="outbound.stream.xhttp.mode === 'stream-up' || outbound.stream.xhttp.mode === 'stream-one'"> v-if="outbound.stream.xhttp.mode === 'stream-up' || outbound.stream.xhttp.mode === 'stream-one'">
<a-switch v-model="outbound.stream.xhttp.noGRPCHeader"></a-switch> <a-switch v-model="outbound.stream.xhttp.noGRPCHeader"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Min Upload Interval (Ms)" <a-form-item label="Min Upload Interval (Ms)" v-if="outbound.stream.xhttp.mode === 'packet-up'">
v-if="outbound.stream.xhttp.mode === 'packet-up'"> <a-input v-model.trim="outbound.stream.xhttp.scMinPostsIntervalMs"></a-input>
<a-input
v-model.trim="outbound.stream.xhttp.scMinPostsIntervalMs"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Max Concurrency" <a-form-item label="Max Concurrency" v-if="!outbound.stream.xhttp.xmux.maxConnections">
v-if="!outbound.stream.xhttp.xmux.maxConnections"> <a-input v-model="outbound.stream.xhttp.xmux.maxConcurrency"></a-input>
<a-input
v-model="outbound.stream.xhttp.xmux.maxConcurrency"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Max Connections" <a-form-item label="Max Connections" v-if="!outbound.stream.xhttp.xmux.maxConcurrency">
v-if="!outbound.stream.xhttp.xmux.maxConcurrency"> <a-input v-model="outbound.stream.xhttp.xmux.maxConnections"></a-input>
<a-input
v-model="outbound.stream.xhttp.xmux.maxConnections"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Max Reuse Times"> <a-form-item label="Max Reuse Times">
<a-input <a-input v-model="outbound.stream.xhttp.xmux.cMaxReuseTimes"></a-input>
v-model="outbound.stream.xhttp.xmux.cMaxReuseTimes"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Max Request Times"> <a-form-item label="Max Request Times">
<a-input <a-input v-model="outbound.stream.xhttp.xmux.hMaxRequestTimes"></a-input>
v-model="outbound.stream.xhttp.xmux.hMaxRequestTimes"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Max Reusable Secs"> <a-form-item label="Max Reusable Secs">
<a-input <a-input v-model="outbound.stream.xhttp.xmux.hMaxReusableSecs"></a-input>
v-model="outbound.stream.xhttp.xmux.hMaxReusableSecs"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Keep Alive Period'> <a-form-item label='Keep Alive Period'>
<a-input-number <a-input-number v-model.number="outbound.stream.xhttp.xmux.hKeepAlivePeriod"></a-input-number>
v-model.number="outbound.stream.xhttp.xmux.hKeepAlivePeriod"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
@ -531,137 +467,210 @@
<a-input v-model.trim="outbound.stream.hysteria.auth"></a-input> <a-input v-model.trim="outbound.stream.hysteria.auth"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Congestion'> <a-form-item label='Congestion'>
<a-select v-model="outbound.stream.hysteria.congestion" <a-select v-model="outbound.stream.hysteria.congestion" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>BBR (Auto)</a-select-option> <a-select-option value>BBR (Auto)</a-select-option>
<a-select-option value="brutal">Brutal</a-select-option> <a-select-option value="brutal">Brutal</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Upload Speed'> <a-form-item label='Upload Speed'>
<a-input v-model.trim="outbound.stream.hysteria.up" <a-input v-model.trim="outbound.stream.hysteria.up" placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Download Speed'> <a-form-item label='Download Speed'>
<a-input v-model.trim="outbound.stream.hysteria.down" <a-input v-model.trim="outbound.stream.hysteria.down" placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='UDP Hop Port'> <a-form-item label='UDP Hop Port'>
<a-input v-model.trim="outbound.stream.hysteria.udphopPort" <a-input v-model.trim="outbound.stream.hysteria.udphopPort"
placeholder="e.g., 1145-1919 or 11,13,15-17"></a-input> placeholder="e.g., 1145-1919 or 11,13,15-17"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='UDP Hop Interval Min (s)' <a-form-item label='UDP Hop Interval Min (s)' v-if="outbound.stream.hysteria.udphopPort">
v-if="outbound.stream.hysteria.udphopPort"> <a-input-number v-model.number="outbound.stream.hysteria.udphopIntervalMin" :min="5"></a-input-number>
<a-input-number
v-model.number="outbound.stream.hysteria.udphopIntervalMin"
:min="5"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='UDP Hop Interval Max (s)' <a-form-item label='UDP Hop Interval Max (s)' v-if="outbound.stream.hysteria.udphopPort">
v-if="outbound.stream.hysteria.udphopPort"> <a-input-number v-model.number="outbound.stream.hysteria.udphopIntervalMax" :min="5"></a-input-number>
<a-input-number
v-model.number="outbound.stream.hysteria.udphopIntervalMax"
:min="5"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Init Stream Receive'> <a-form-item label='Init Stream Receive'>
<a-input-number <a-input-number v-model.number="outbound.stream.hysteria.initStreamReceiveWindow"></a-input-number>
v-model.number="outbound.stream.hysteria.initStreamReceiveWindow"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Max Stream Receive'> <a-form-item label='Max Stream Receive'>
<a-input-number <a-input-number v-model.number="outbound.stream.hysteria.maxStreamReceiveWindow"></a-input-number>
v-model.number="outbound.stream.hysteria.maxStreamReceiveWindow"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Init Connection Receive'> <a-form-item label='Init Connection Receive'>
<a-input-number <a-input-number v-model.number="outbound.stream.hysteria.initConnectionReceiveWindow"></a-input-number>
v-model.number="outbound.stream.hysteria.initConnectionReceiveWindow"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Max Connection Receive'> <a-form-item label='Max Connection Receive'>
<a-input-number <a-input-number v-model.number="outbound.stream.hysteria.maxConnectionReceiveWindow"></a-input-number>
v-model.number="outbound.stream.hysteria.maxConnectionReceiveWindow"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Max Idle Timeout (s)'> <a-form-item label='Max Idle Timeout (s)'>
<a-input-number <a-input-number v-model.number="outbound.stream.hysteria.maxIdleTimeout" :min="4"
v-model.number="outbound.stream.hysteria.maxIdleTimeout" :min="4"
:max="120"></a-input-number> :max="120"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Keep Alive Period (s)'> <a-form-item label='Keep Alive Period (s)'>
<a-input-number <a-input-number v-model.number="outbound.stream.hysteria.keepAlivePeriod" :min="0"
v-model.number="outbound.stream.hysteria.keepAlivePeriod" :min="0"
:max="60"></a-input-number> :max="60"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Disable Path MTU'> <a-form-item label='Disable Path MTU'>
<a-switch <a-switch v-model="outbound.stream.hysteria.disablePathMTUDiscovery"></a-switch>
v-model="outbound.stream.hysteria.disablePathMTUDiscovery"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- finalmask settings --> <!-- finalmask settings -->
<template v-if="outbound.canEnableStream()"> <template v-if="outbound.canEnableStream()">
<a-form-item label="UDP Masks"> <a-form-item label="UDP Masks"
v-if="outbound.stream.network === 'kcp' || outbound.protocol === Protocols.Hysteria">
<a-button icon="plus" type="primary" size="small" <a-button icon="plus" type="primary" size="small"
@click="outbound.stream.addUdpMask(outbound.protocol === Protocols.Hysteria ? 'salamander' : (outbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns'))"></a-button> @click="outbound.stream.addUdpMask(outbound.protocol === Protocols.Hysteria ? 'salamander' : 'mkcp-aes128gcm')"></a-button>
</a-form-item> </a-form-item>
<template <template v-if="outbound.stream.finalmask.udp && outbound.stream.finalmask.udp.length > 0">
v-if="outbound.stream.finalmask.udp && outbound.stream.finalmask.udp.length > 0"> <a-form v-for="(mask, index) in outbound.stream.finalmask.udp" :key="index" :colon="false"
<a-form v-for="(mask, index) in outbound.stream.finalmask.udp"
:key="index" :colon="false"
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]] <a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
<a-icon type="delete" <a-icon type="delete" @click="() => outbound.stream.delUdpMask(index)"
@click="() => outbound.stream.delUdpMask(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon> :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider> </a-divider>
<a-form-item label='Type'> <a-form-item label='Type'>
<a-select v-model="mask.type" <a-select v-model="mask.type"
@change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); if(outbound.stream.network === 'kcp') { outbound.stream.kcp.mtu = type === 'xdns' ? 900 : 1350; } }" @change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); if(outbound.stream.network === 'kcp') { outbound.stream.kcp.mtu = type === 'xdns' ? 900 : 1350; } }"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<!-- Salamander for Hysteria2 only --> <a-select-option v-if="outbound.protocol === Protocols.Hysteria" value="salamander">
<a-select-option v-if="outbound.protocol === Protocols.Hysteria"
value="salamander">
Salamander (Hysteria2)</a-select-option> Salamander (Hysteria2)</a-select-option>
<!-- mKCP-specific masks --> <template v-else>
<a-select-option v-if="outbound.stream.network === 'kcp'" <a-select-option value="mkcp-aes128gcm">mKCP AES-128-GCM</a-select-option>
value="mkcp-aes128gcm"> <a-select-option value="header-dns">Header DNS</a-select-option>
mKCP AES-128-GCM</a-select-option> <a-select-option value="header-dtls">Header DTLS 1.2</a-select-option>
<a-select-option v-if="outbound.stream.network === 'kcp'" <a-select-option value="header-srtp">Header SRTP</a-select-option>
value="header-dns"> <a-select-option value="header-utp">Header uTP</a-select-option>
Header DNS</a-select-option> <a-select-option value="header-wechat">Header WeChat Video</a-select-option>
<a-select-option v-if="outbound.stream.network === 'kcp'" <a-select-option value="header-wireguard">Header WireGuard</a-select-option>
value="header-dtls"> <a-select-option value="mkcp-original">mKCP Original</a-select-option>
Header DTLS 1.2</a-select-option> <a-select-option value="xdns">xDNS</a-select-option>
<a-select-option v-if="outbound.stream.network === 'kcp'" <a-select-option value="xicmp">xICMP</a-select-option>
value="header-srtp"> <a-select-option value="header-custom">Header Custom</a-select-option>
Header SRTP</a-select-option> <a-select-option value="noise">Noise</a-select-option>
<a-select-option v-if="outbound.stream.network === 'kcp'" <a-select-option value="sudoku">Sudoku</a-select-option>
value="header-utp"> </template>
Header uTP</a-select-option>
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="header-wechat">
Header WeChat Video</a-select-option>
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="header-wireguard">
Header WireGuard</a-select-option>
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="mkcp-original">
mKCP Original</a-select-option>
<!-- xDNS for TCP/WS/HTTPUpgrade/XHTTP/KCP -->
<a-select-option
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(outbound.stream.network)"
value="xdns">
xDNS (Experimental)</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<!-- Settings for password-based masks --> <a-form-item label='Password' v-if="['salamander', 'mkcp-aes128gcm', 'sudoku'].includes(mask.type)">
<a-form-item label='Password' <a-input v-model.trim="mask.settings.password" placeholder="Obfuscation password"></a-input>
v-if="['salamander', 'mkcp-aes128gcm'].includes(mask.type)">
<a-input v-model.trim="mask.settings.password"
placeholder="Obfuscation password"></a-input>
</a-form-item> </a-form-item>
<!-- Settings for domain-based masks --> <a-form-item label='Domain' v-if="['header-dns', 'xdns'].includes(mask.type)">
<a-form-item label='Domain' <a-input v-model.trim="mask.settings.domain" placeholder="e.g., www.example.com"></a-input>
v-if="['header-dns', 'xdns'].includes(mask.type)">
<a-input v-model.trim="mask.settings.domain"
placeholder="e.g., www.example.com"></a-input>
</a-form-item> </a-form-item>
<template v-if="mask.type === 'header-custom'">
<a-form-item label='Client'>
<a-icon type="plus" type="primary" size="small"
@click="mask.settings.client.push({rand: 0, randRange: '0-255', type: 'array', packet: []})" />
</a-form-item>
<template v-for="(c, index) in mask.settings.client" :key="index">
<a-divider :style="{ margin: '0' }"> Client [[ index + 1 ]]
<a-icon type="delete" @click="() => mask.settings.client.splice(index, 1)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Rand'>
<a-input-number v-model.number="c.rand" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='Rand Range'>
<a-input v-model.trim="c.randRange" placeholder="0-255"></a-input>
</a-form-item>
<a-form-item label='Type'>
<a-select v-model="c.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="array">Array</a-select-option>
<a-select-option value="str">String</a-select-option>
<a-select-option value="hex">Hex</a-select-option>
<a-select-option value="base64">Base64</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Packet'>
<a-input v-model.trim="c.packet" placeholder="binary data" />
</a-form-item>
</template>
<a-divider :style="{ margin: '0' }"></a-divider>
<a-form-item label='Server'>
<a-icon type="plus" type="primary" size="small"
@click="mask.settings.server.push({rand: 0, randRange: '0-255', type: 'array', packet: []})" />
</a-form-item>
<template v-for="(s, index) in mask.settings.server" :key="index">
<a-divider :style="{ margin: '0' }"> Server [[ index + 1 ]]
<a-icon type="delete" @click="() => mask.settings.server.splice(index, 1)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Rand'>
<a-input-number v-model.number="s.rand" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='Rand Range'>
<a-input v-model.trim="s.randRange" placeholder="0-255"></a-input>
</a-form-item>
<a-form-item label='Type'>
<a-select v-model="s.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="array">Array</a-select-option>
<a-select-option value="str">String</a-select-option>
<a-select-option value="hex">Hex</a-select-option>
<a-select-option value="base64">Base64</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Packet'>
<a-input v-model.trim="s.packet" placeholder="binary data" />
</a-form-item>
</template>
</template>
<template v-if="mask.type === 'sudoku'">
<a-form-item label='ASCII'>
<a-input v-model.trim="mask.settings.ascii" placeholder="ASCII"></a-input>
</a-form-item>
<a-form-item label='Custom Table'>
<a-input v-model.trim="mask.settings.customTable" placeholder="Custom Table"></a-input>
</a-form-item>
<a-form-item label='Custom Tables'>
<a-input v-model.trim="mask.settings.customTables" placeholder="Custom Tables"></a-input>
</a-form-item>
<a-form-item label='Padding Min'>
<a-input-number v-model.number="mask.settings.paddingMin" :min="0"></a-input-number>
</a-form-item>
</template>
<template v-if="mask.type === 'noise'">
<a-form-item label='Reset'>
<a-input-number v-model.number="mask.settings.reset" :min="0" />
</a-form-item>
<a-form-item label='Noise'>
<a-icon type="plus" type="primary" size="small"
@click="mask.settings.noise.push({rand: '1-8192', randRange: '0-255', type: 'array', packet: '', delay: ''})" />
</a-form-item>
<template v-for="(n, index) in mask.settings.noise" :key="index">
<a-divider :style="{ margin: '0' }"> Noise [[ index + 1 ]]
<a-icon type="delete" @click="() => mask.settings.noise.splice(index, 1)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Rand'>
<a-input-number v-model.number="n.rand" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='Rand Range'>
<a-input v-model.trim="n.randRange" placeholder="0-255"></a-input>
</a-form-item>
<a-form-item label='Type'>
<a-select v-model="n.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="array">Array</a-select-option>
<a-select-option value="str">String</a-select-option>
<a-select-option value="hex">Hex</a-select-option>
<a-select-option value="base64">Base64</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Packet'>
<a-input v-model.trim="n.packet" placeholder="binary data" />
</a-form-item>
<a-form-item label='Delay'>
<a-input v-model.trim="n.delay" placeholder="10-20" />
</a-form-item>
</template>
</template>
<template v-if="mask.type === 'xicmp'">
<a-form-item label='IP'>
<a-input v-model.trim="mask.settings.ip" placeholder="0.0.0.0"></a-input>
</a-form-item>
<a-form-item label='ID'>
<a-input-number v-model.number="mask.settings.id" :min="0"></a-input-number>
</a-form-item>
</template>
</a-form> </a-form>
</template> </template>
</template> </template>
@ -669,12 +678,10 @@
<!-- tls settings --> <!-- tls settings -->
<template v-if="outbound.canEnableTls()"> <template v-if="outbound.canEnableTls()">
<a-form-item label='{{ i18n "security" }}'> <a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="outbound.stream.security" <a-radio-group v-model="outbound.stream.security" button-style="solid">
button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button> <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="tls">TLS</a-radio-button> <a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" <a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
value="reality">Reality</a-radio-button>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.isTls"> <template v-if="outbound.stream.isTls">
@ -682,16 +689,14 @@
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input> <a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="outbound.stream.tls.fingerprint" <a-select v-model="outbound.stream.tls.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>None</a-select-option> <a-select-option value>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[
key ]]</a-select-option> key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="ALPN"> <a-form-item label="ALPN">
<a-select mode="multiple" <a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="outbound.stream.tls.alpn"> v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn
]]</a-select-option> ]]</a-select-option>
@ -701,9 +706,7 @@
<a-input v-model.trim="outbound.stream.tls.echConfigList"></a-input> <a-input v-model.trim="outbound.stream.tls.echConfigList"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="verify Peer Cert By Name"> <a-form-item label="verify Peer Cert By Name">
<a-input <a-input v-model.trim="outbound.stream.tls.verifyPeerCertByName" placeholder="cloudflare-dns.com"></a-input>
v-model.trim="outbound.stream.tls.verifyPeerCertByName"
placeholder="cloudflare-dns.com"></a-input>
</a-form-item> </a-form-item>
<a-form-item label=" pinned Peer Cert Sha256"> <a-form-item label=" pinned Peer Cert Sha256">
<a-input v-model.trim="outbound.stream.tls.pinnedPeerCertSha256" <a-input v-model.trim="outbound.stream.tls.pinnedPeerCertSha256"
@ -715,12 +718,10 @@
<!-- reality settings --> <!-- reality settings -->
<template v-if="outbound.stream.isReality"> <template v-if="outbound.stream.isReality">
<a-form-item label="SNI"> <a-form-item label="SNI">
<a-input <a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
v-model.trim="outbound.stream.reality.serverName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="outbound.stream.reality.fingerprint" <a-select v-model="outbound.stream.reality.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[
key ]]</a-select-option> key ]]</a-select-option>
</a-select> </a-select>
@ -732,12 +733,10 @@
<a-input v-model.trim="outbound.stream.reality.spiderX"></a-input> <a-input v-model.trim="outbound.stream.reality.spiderX"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Public Key"> <a-form-item label="Public Key">
<a-textarea <a-textarea v-model.trim="outbound.stream.reality.publicKey"></a-textarea>
v-model.trim="outbound.stream.reality.publicKey"></a-textarea>
</a-form-item> </a-form-item>
<a-form-item label="mldsa65 Verify"> <a-form-item label="mldsa65 Verify">
<a-textarea <a-textarea v-model.trim="outbound.stream.reality.mldsa65Verify"></a-textarea>
v-model.trim="outbound.stream.reality.mldsa65Verify"></a-textarea>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
@ -748,44 +747,34 @@
</a-form-item> </a-form-item>
<template v-if="outbound.stream.sockoptSwitch"> <template v-if="outbound.stream.sockoptSwitch">
<a-form-item label="Dialer Proxy"> <a-form-item label="Dialer Proxy">
<a-select v-model="outbound.stream.sockopt.dialerProxy" <a-select v-model="outbound.stream.sockopt.dialerProxy" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
<a-select-option v-for="tag in ['', ...outModal.tags]"
:value="tag">[[ tag ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Address Port Strategy'> <a-form-item label='Address Port Strategy'>
<a-select v-model="outbound.stream.sockopt.addressPortStrategy" <a-select v-model="outbound.stream.sockopt.addressPortStrategy"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in Address_Port_Strategy" <a-select-option v-for="key in Address_Port_Strategy" :value="key">[[ key ]]</a-select-option>
:value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Keep Alive Interval"> <a-form-item label="Keep Alive Interval">
<a-input-number <a-input-number v-model.number="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
v-model.number="outbound.stream.sockopt.tcpKeepAliveInterval"
:min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="TCP Fast Open"> <a-form-item label="TCP Fast Open">
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch> <a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Multipath TCP"> <a-form-item label="Multipath TCP">
<a-switch <a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Penetrate"> <a-form-item label="Penetrate">
<a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch> <a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Trusted X-Forwarded-For"> <a-form-item label="Trusted X-Forwarded-For">
<a-select mode="tags" <a-select mode="tags" v-model="outbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }"
v-model="outbound.stream.sockopt.trustedXForwardedFor"
:style="{ width: '100%' }"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option <a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option> <a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
<a-select-option <a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
value="True-Client-IP">True-Client-IP</a-select-option>
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option> <a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -798,19 +787,14 @@
</a-form-item> </a-form-item>
<template v-if="outbound.mux.enabled"> <template v-if="outbound.mux.enabled">
<a-form-item label="Concurrency"> <a-form-item label="Concurrency">
<a-input-number v-model.number="outbound.mux.concurrency" <a-input-number v-model.number="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
:min="-1"
:max="1024"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="xudp Concurrency"> <a-form-item label="xudp Concurrency">
<a-input-number v-model.number="outbound.mux.xudpConcurrency" <a-input-number v-model.number="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
:min="-1" :max="1024"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="xudp UDP 443"> <a-form-item label="xudp UDP 443">
<a-select v-model="outbound.mux.xudpProxyUDP443" <a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option v-for="c in ['reject', 'allow', 'skip']" :value="c">[[ c ]]</a-select-option>
<a-select-option v-for="c in ['reject', 'allow', 'skip']"
:value="c">[[ c ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
@ -819,13 +803,11 @@
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="JSON" force-render="true"> <a-tab-pane key="2" tab="JSON" force-render="true">
<a-space direction="vertical" :size="10" :style="{ marginTop: '10px' }"> <a-space direction="vertical" :size="10" :style="{ marginTop: '10px' }">
<a-input addon-before='{{ i18n "pages.xray.outbound.link" }}' <a-input addon-before='{{ i18n "pages.xray.outbound.link" }}' v-model.trim="outModal.link"
v-model.trim="outModal.link"
placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"> placeholder="vmess:// vless:// trojan:// ss:// hysteria2://">
<a-icon slot="addonAfter" type="form" @click="convertLink"></a-icon> <a-icon slot="addonAfter" type="form" @click="convertLink"></a-icon>
</a-input> </a-input>
<textarea :style="{ position: 'absolute', left: '-800px' }" <textarea :style="{ position: 'absolute', left: '-800px' }" id="outboundJson"></textarea>
id="outboundJson"></textarea>
</a-space> </a-space>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>

View file

@ -1,50 +1,76 @@
{{define "form/shadowsocks"}} {{define "form/shadowsocks"}}
<template v-if="inbound.isSSMultiUser"> <template v-if="inbound.isSSMultiUser">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit"> <a-collapse
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'> activeKey="0"
{{template "form/client"}} v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)"
</a-collapse-panel> v-if="!isEdit"
</a-collapse> >
<a-collapse v-else> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length"> {{template "form/client"}}
<table width="100%"> </a-collapse-panel>
<tr class="client-table-header"> </a-collapse>
<th>{{ i18n "pages.inbounds.email" }}</th> <a-collapse v-else>
<th>Password</th> <a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
</tr> <table width="100%">
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''"> <tr class="client-table-header">
<td>[[ client.email ]]</td> <th>{{ i18n "pages.inbounds.email" }}</th>
<td>[[ client.password ]]</td> <th>Password</th>
</tr> </tr>
</table> <tr
</a-collapse-panel> v-for="(client, index) in inbound.settings.shadowsockses"
</a-collapse> :class="index % 2 == 1 ? ' client-table-odd-row' : ''"
>
<td>[[ client.email ]]</td>
<td>[[ client.password ]]</td>
</tr>
</table>
</a-collapse-panel>
</a-collapse>
</template> </template>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form
<a-form-item label='{{ i18n "encryption" }}'> :colon=" false"
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme"> :label-col="{ md: {span:8} }"
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option> :wrapper-col="{ md: {span:14} }"
</a-select> >
</a-form-item> <a-form-item label='{{ i18n "encryption" }}'>
<a-form-item v-if="inbound.isSS2022"> <a-select
<template slot="label"> v-model="inbound.settings.method"
<a-tooltip> @change="SSMethodChange"
<template slot="title"> :dropdown-class-name="themeSwitcher.currentTheme"
<span>{{ i18n "reset" }}</span> >
</template> Password <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)" type="sync"></a-icon> <a-select-option v-for="(method,method_name) in SSMethods" :value="method"
</a-tooltip> >[[ method_name ]]</a-select-option
>
</a-select>
</a-form-item>
<a-form-item v-if="inbound.isSS2022">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template> </template>
<a-input v-model.trim="inbound.settings.password"></a-input> Password
</a-form-item> <a-icon
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)"
<a-select v-model="inbound.settings.network" :style="{ width: '100px' }" :dropdown-class-name="themeSwitcher.currentTheme"> type="sync"
<a-select-option value="tcp,udp">TCP,UDP</a-select-option> ></a-icon>
<a-select-option value="tcp">TCP</a-select-option> </a-tooltip>
<a-select-option value="udp">UDP</a-select-option> </template>
</a-select> <a-input v-model.trim="inbound.settings.password"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='ivCheck'> <a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-switch v-model="inbound.settings.ivCheck"></a-switch> <a-select
</a-form-item> v-model="inbound.settings.network"
:style="{ width: '100px' }"
:dropdown-class-name="themeSwitcher.currentTheme"
>
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="udp">UDP</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ivCheck">
<a-switch v-model="inbound.settings.ivCheck"></a-switch>
</a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -1,5 +1,9 @@
{{define "form/mixed"}} {{define "form/mixed"}}
<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='{{ i18n "pages.inbounds.enable" }} UDP'> <a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
<a-switch v-model="inbound.settings.udp"></a-switch> <a-switch v-model="inbound.settings.udp"></a-switch>
</a-form-item> </a-form-item>
@ -7,7 +11,10 @@
<a-input v-model.trim="inbound.settings.ip"></a-input> <a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-switch :checked="inbound.settings.auth === 'password'" @change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch> <a-switch
:checked="inbound.settings.auth === 'password'"
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"
></a-switch>
</a-form-item> </a-form-item>
<template v-if="inbound.settings.auth === 'password'"> <template v-if="inbound.settings.auth === 'password'">
<table :style="{ width: '100%', textAlign: 'center', margin: '1rem 0' }"> <table :style="{ width: '100%', textAlign: 'center', margin: '1rem 0' }">
@ -15,17 +22,39 @@
<td width="45%">{{ i18n "username" }}</td> <td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td> <td width="45%">{{ i18n "password" }}</td>
<td> <td>
<a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.MixedSettings.SocksAccount())"></a-button> <a-button
icon="plus"
size="small"
@click="inbound.settings.addAccount(new Inbound.MixedSettings.SocksAccount())"
></a-button>
</td> </td>
</tr> </tr>
</table> </table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" :style="{ marginBottom: '10px' }"> <a-input-group
<a-input :style="{ width: '50%' }" v-model.trim="account.user" placeholder='{{ i18n "username" }}'> compact
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template> v-for="(account, index) in inbound.settings.accounts"
:style="{ marginBottom: '10px' }"
>
<a-input
:style="{ width: '50%' }"
v-model.trim="account.user"
placeholder='{{ i18n "username" }}'
>
<template slot="addonBefore" :style="{ margin: '0' }"
>[[ index+1 ]]</template
>
</a-input> </a-input>
<a-input :style="{ width: '50%' }" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'> <a-input
:style="{ width: '50%' }"
v-model.trim="account.pass"
placeholder='{{ i18n "password" }}'
>
<template slot="addonAfter"> <template slot="addonAfter">
<a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button> <a-button
icon="minus"
size="small"
@click="inbound.settings.delAccount(index)"
></a-button>
</template> </template>
</a-input> </a-input>
</a-input-group> </a-input-group>

View file

@ -11,40 +11,43 @@
<th>{{ i18n "pages.inbounds.email" }}</th> <th>{{ i18n "pages.inbounds.email" }}</th>
<th>Password</th> <th>Password</th>
</tr> </tr>
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''"> <tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? ' client-table-odd-row' : ''">
<td>[[ client.email ]]</td> <td>[[ client.email ]]</td>
<td>[[ client.password ]]</td> <td>[[ client.password ]]</td>
</tr> </tr>
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp"> <template v-if=" inbound.isTcp">
<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="Fallbacks"> <a-form-item label="Fallbacks">
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button> <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- trojan fallbacks --> <!-- trojan fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
<a-divider :style="{ margin: '0' }"> Fallback [[ index + 1 ]] <a-icon type="delete" @click="() => inbound.settings.delFallback(index)" :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon> :wrapper-col="{ md: {span:14} }">
</a-divider> <a-divider :style="{ margin: '0' }"> Fallback [[ index + 1 ]] <a-icon type="delete"
<a-form-item label='SNI'> @click="() => inbound.settings.delFallback(index)"
<a-input v-model="fallback.name"></a-input> :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-form-item> </a-divider>
<a-form-item label='ALPN'> <a-form-item label='SNI'>
<a-input v-model="fallback.alpn"></a-input> <a-input v-model="fallback.name"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Path'> <a-form-item label='ALPN'>
<a-input v-model="fallback.path"></a-input> <a-input v-model="fallback.alpn"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Dest'> <a-form-item label='Path'>
<a-input v-model="fallback.dest"></a-input> <a-input v-model="fallback.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='xVer'> <a-form-item label='Dest'>
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number> <a-input v-model="fallback.dest"></a-input>
</a-form-item> </a-form-item>
</a-form> <a-form-item label='xVer'>
<a-divider style="margin:5px 0;"></a-divider> <a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
</template> </a-form-item>
{{end}} </a-form>
<a-divider style="margin:5px 0;"></a-divider>
</template>
{{end}}

View file

@ -1,44 +1,53 @@
{{define "form/tun"}} {{define "form/tun"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" <a-form
:wrapper-col="{ md: {span:14} }"> :colon="false"
<a-form-item> :label-col="{ md: {span:8} }"
<template slot="label"> :wrapper-col="{ md: {span:14} }"
<a-tooltip> >
<template slot="title"> <a-form-item>
<span>{{ i18n "pages.xray.tun.nameDesc" }}</span> <template slot="label">
</template> <a-tooltip>
Interface Name <template slot="title">
<a-icon type="question-circle"></a-icon> <span>{{ i18n "pages.xray.tun.nameDesc" }}</span>
</a-tooltip>
</template> </template>
<a-input v-model.trim="inbound.settings.name" Interface Name
placeholder="xray0"></a-input> <a-icon type="question-circle"></a-icon>
</a-form-item> </a-tooltip>
<a-form-item> </template>
<template slot="label"> <a-input v-model.trim="inbound.settings.name" placeholder="xray0"></a-input>
<a-tooltip> </a-form-item>
<template slot="title"> <a-form-item>
<span>{{ i18n "pages.xray.tun.mtuDesc" }}</span> <template slot="label">
</template> <a-tooltip>
MTU <template slot="title">
<a-icon type="question-circle"></a-icon> <span>{{ i18n "pages.xray.tun.mtuDesc" }}</span>
</a-tooltip>
</template> </template>
<a-input-number v-model.number="inbound.settings.mtu" :min="1" MTU
:max="9000" placeholder="1500"></a-input-number> <a-icon type="question-circle"></a-icon>
</a-form-item> </a-tooltip>
<a-form-item> </template>
<template slot="label"> <a-input-number
<a-tooltip> v-model.number="inbound.settings.mtu"
<template slot="title"> :min="1"
<span>{{ i18n "pages.xray.tun.userLevelDesc" }}</span> :max="9000"
</template> placeholder="1500"
{{ i18n "pages.xray.tun.userLevel" }} ></a-input-number>
<a-icon type="question-circle"></a-icon> </a-form-item>
</a-tooltip> <a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.tun.userLevelDesc" }}</span>
</template> </template>
<a-input-number v-model.number="inbound.settings.userLevel" :min="0" {{ i18n "pages.xray.tun.userLevel" }}
placeholder="0"></a-input-number> <a-icon type="question-circle"></a-icon>
</a-form-item> </a-tooltip>
</template>
<a-input-number
v-model.number="inbound.settings.userLevel"
:min="0"
placeholder="0"
></a-input-number>
</a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -120,4 +120,4 @@
</a-form> </a-form>
<a-divider :style="{ margin: '5px 0' }"></a-divider> <a-divider :style="{ margin: '5px 0' }"></a-divider>
</template> </template>
{{end}} {{end}}

View file

@ -1,5 +1,5 @@
{{define "form/vmess"}} {{define "form/vmess"}}
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}} {{template "form/client"}}
</a-collapse-panel> </a-collapse-panel>
@ -12,7 +12,8 @@
<th>ID</th> <th>ID</th>
<th>{{ i18n "security" }}</th> <th>{{ i18n "security" }}</th>
</tr> </tr>
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''"> <tr v-for="(client, index) in inbound.settings.vmesses"
:class="index % 2 == 1 ? ' client-table-odd-row' : ''">
<td>[[ client.email ]]</td> <td>[[ client.email ]]</td>
<td>[[ client.id ]]</td> <td>[[ client.id ]]</td>
<td>[[ client.security ]]</td> <td>[[ client.security ]]</td>
@ -20,4 +21,4 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
{{end}} {{end}}

View file

@ -7,7 +7,8 @@
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
{{ i18n "pages.xray.wireguard.secretKey" }} {{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync" @click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())"></a-icon> <a-icon type="sync"
@click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="inbound.settings.secretKey"></a-input> <a-input v-model.trim="inbound.settings.secretKey"></a-input>
@ -24,8 +25,11 @@
<a-form-item label="Peers"> <a-form-item label="Peers">
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addPeer()"></a-button> <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addPeer()"></a-button>
</a-form-item> </a-form-item>
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }"
<a-divider :style="{ margin: '0' }"> Peer [[ index + 1 ]] <a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)" :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon> :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> Peer [[ index + 1 ]] <a-icon v-if="inbound.settings.peers.length>1"
type="delete" @click="() => inbound.settings.delPeer(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider> </a-divider>
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
@ -34,7 +38,8 @@
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
{{ i18n "pages.xray.wireguard.secretKey" }} {{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())" type="sync"></a-icon> <a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"
type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="peer.privateKey"></a-input> <a-input v-model.trim="peer.privateKey"></a-input>
@ -64,7 +69,8 @@
</template> </template>
<template v-for="(aip, index) in peer.allowedIPs" :style="{ marginBottom: '10px' }"> <template v-for="(aip, index) in peer.allowedIPs" :style="{ marginBottom: '10px' }">
<a-input v-model.trim="peer.allowedIPs[index]"> <a-input v-model.trim="peer.allowedIPs[index]">
<a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button> <a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small"
@click="peer.allowedIPs.splice(index, 1)"></a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>

View file

@ -17,8 +17,7 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> Target <a-icon @click="randomizeRealityTarget()" </template> Target <a-icon @click="randomizeRealityTarget()" type="sync"></a-icon>
type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="inbound.stream.reality.target"></a-input> <a-input v-model.trim="inbound.stream.reality.target"></a-input>
@ -28,8 +27,7 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> SNI <a-icon @click="randomizeRealityTarget()" </template> SNI <a-icon @click="randomizeRealityTarget()" type="sync"></a-icon>
type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input> <a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>

View file

@ -1,84 +1,167 @@
{{define "form/streamFinalMask"}} {{define "form/streamFinalMask"}}
<a-divider :style="{ margin: '5px 0 0' }"></a-divider> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"
<a-form :colon="false" :label-col="{ md: {span:8} }" v-if="inbound.protocol == Protocols.HYSTERIA || inbound.stream.network == 'kcp'">
:wrapper-col="{ md: {span:14} }"> <a-divider :style="{ margin: '5px 0 0' }"></a-divider>
<a-form-item label="UDP Masks"> <a-form-item label="UDP Masks">
<a-button icon="plus" type="primary" size="small" <a-button icon="plus" type="primary" size="small"
@click="inbound.stream.addUdpMask(inbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns')"></a-button> @click="inbound.stream.addUdpMask(inbound.protocol === Protocols.HYSTERIA ? 'salamander' : 'mkcp-aes128gcm')"></a-button>
</a-form-item> </a-form-item>
<template <template v-if="inbound.stream.finalmask.udp && inbound.stream.finalmask.udp.length > 0">
v-if="inbound.stream.finalmask.udp && inbound.stream.finalmask.udp.length > 0"> <a-form v-for="(mask, index) in inbound.stream.finalmask.udp" :key="index" :colon="false"
<a-form v-for="(mask, index) in inbound.stream.finalmask.udp"
:key="index" :colon="false"
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]] <a-divider :style="{ margin: '0' }">
<a-icon type="delete" UDP Mask [[ index + 1 ]]
@click="() => inbound.stream.delUdpMask(index)" <a-icon type="delete" @click="() => inbound.stream.delUdpMask(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon> :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider> </a-divider>
<a-form-item label='Type'> <a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
<a-select v-model="mask.type" <a-select v-model="mask.type"
@change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); if(inbound.stream.network === 'kcp') { inbound.stream.kcp.mtu = type === 'xdns' ? 900 : 1350; } }" @change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); if(inbound.stream.network === 'kcp') { inbound.stream.kcp.mtu = type === 'xdns' ? 900 : 1350; } }"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<!-- mKCP-specific masks --> <template v-if="inbound.protocol === Protocols.HYSTERIA">
<a-select-option v-if="inbound.stream.network === 'kcp'" <a-select-option value="salamander">Salamander (Hysteria2)</a-select-option>
value="mkcp-aes128gcm"> </template>
mKCP AES-128-GCM</a-select-option> <template v-else>
<a-select-option v-if="inbound.stream.network === 'kcp'" <a-select-option value="mkcp-aes128gcm">mKCP AES-128-GCM</a-select-option>
value="header-dns"> <a-select-option value="header-dns">Header DNS</a-select-option>
Header DNS</a-select-option> <a-select-option value="header-dtls">Header DTLS 1.2</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'" <a-select-option value="header-srtp">Header SRTP</a-select-option>
value="header-dtls"> <a-select-option value="header-utp">Header uTP</a-select-option>
Header DTLS 1.2</a-select-option> <a-select-option value="header-wechat">Header WeChat Video</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'" <a-select-option value="header-wireguard">Header WireGuard</a-select-option>
value="header-srtp"> <a-select-option value="mkcp-original">mKCP Original</a-select-option>
Header SRTP</a-select-option> <a-select-option value="xdns">xDNS</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'" <a-select-option value="xicmp">xICMP</a-select-option>
value="header-utp"> <a-select-option value="header-custom">Header Custom</a-select-option>
Header uTP</a-select-option> <a-select-option value="noise">Noise</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'" <a-select-option value="sudoku">Sudoku</a-select-option>
value="header-wechat"> </template>
Header WeChat Video</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-wireguard">
Header WireGuard</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="mkcp-original">
mKCP Original</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="xicmp">
xICMP (Experimental)</a-select-option>
<!-- xDNS for TCP/WS/HTTPUpgrade/XHTTP/KCP -->
<a-select-option
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(inbound.stream.network)"
value="xdns">
xDNS (Experimental)</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<!-- Settings for password-based masks --> <a-form-item label="Password" v-if="['mkcp-aes128gcm', 'salamander', 'sudoku'].includes(mask.type)">
<a-form-item label='Password' <a-input v-model.trim="mask.settings.password" placeholder="Obfuscation password"></a-input>
v-if="['mkcp-aes128gcm'].includes(mask.type)">
<a-input v-model.trim="mask.settings.password"
placeholder="Obfuscation password"></a-input>
</a-form-item> </a-form-item>
<!-- Settings for domain-based masks --> <a-form-item label="Domain" v-if="['header-dns', 'xdns'].includes(mask.type)">
<a-form-item label='Domain' <a-input v-model.trim="mask.settings.domain" placeholder="e.g., www.example.com"></a-input>
v-if="['header-dns', 'xdns'].includes(mask.type)">
<a-input v-model.trim="mask.settings.domain"
placeholder="e.g., www.example.com"></a-input>
</a-form-item>
<!-- Settings for xICMP -->
<a-form-item label='IP'
v-if="mask.type === 'xicmp'">
<a-input v-model.trim="mask.settings.ip"
placeholder="e.g., 1.1.1.1"></a-input>
</a-form-item>
<a-form-item label='ID'
v-if="mask.type === 'xicmp'">
<a-input-number v-model.number="mask.settings.id"
:min="0" :max="65535"></a-input-number>
</a-form-item> </a-form-item>
<template v-if="mask.type === 'header-custom'">
<a-form-item label='Client'>
<a-icon type="plus" type="primary" size="small"
@click="mask.settings.client.push({rand: 0, randRange: '0-255', type: 'array', packet: []})" />
</a-form-item>
<template v-for="(c, index) in mask.settings.client" :key="index">
<a-divider :style="{ margin: '0' }"> Client [[ index + 1 ]]
<a-icon type="delete" @click="() => mask.settings.client.splice(index, 1)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Rand'>
<a-input-number v-model.number="c.rand" />
</a-form-item>
<a-form-item label='Rand Range'>
<a-input v-model.trim="c.randRange" placeholder="0-255" />
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
<a-select v-model="c.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="array">Array</a-select-option>
<a-select-option value="str">String</a-select-option>
<a-select-option value="hex">Hex</a-select-option>
<a-select-option value="base64">Base64</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Packet'>
<a-input v-model.trim="c.packet" placeholder="binary data" />
</a-form-item>
</template>
<a-divider :style="{ margin: '0' }"></a-divider>
<a-form-item label='Server'>
<a-icon type="plus" type="primary" size="small"
@click="mask.settings.server.push({rand: 0, randRange: '0-255', type: 'array', packet: []})" />
</a-form-item>
<template v-for="(s, index) in mask.settings.server" :key="index">
<a-divider :style="{ margin: '0' }"> Server [[ index + 1 ]]
<a-icon type="delete" @click="() => mask.settings.server.splice(index, 1)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Rand'>
<a-input-number v-model.number="s.rand" />
</a-form-item>
<a-form-item label='Rand Range'>
<a-input v-model.trim="s.randRange" placeholder="0-255" />
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
<a-select v-model="s.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="array">Array</a-select-option>
<a-select-option value="str">String</a-select-option>
<a-select-option value="hex">Hex</a-select-option>
<a-select-option value="base64">Base64</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Packet'>
<a-input v-model.trim="s.packet" placeholder="binary data" />
</a-form-item>
</template>
</template>
<template v-if="mask.type === 'noise'">
<a-form-item label='Reset'>
<a-input-number v-model.number="mask.settings.reset" :min="0" />
</a-form-item>
<a-form-item label='Noise'>
<a-icon type="plus" type="primary" size="small"
@click="mask.settings.noise.push({rand: '1-8192', randRange: '0-255', type: 'array', packet: '', delay: ''})" />
</a-form-item>
<template v-for="(n, index) in mask.settings.noise" :key="index">
<a-divider :style="{ margin: '0' }"> Noise [[ index + 1 ]]
<a-icon type="delete" @click="() => mask.settings.noise.splice(index, 1)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Rand'>
<a-input v-model.trim="n.rand" placeholder="1-8192" />
</a-form-item>
<a-form-item label='Rand Range'>
<a-input v-model.trim="n.randRange" placeholder="0-255" />
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
<a-select v-model="n.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="array">Array</a-select-option>
<a-select-option value="str">String</a-select-option>
<a-select-option value="hex">Hex</a-select-option>
<a-select-option value="base64">Base64</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Packet'>
<a-input v-model.trim="n.packet" placeholder="binary data" />
</a-form-item>
<a-form-item label='Delay'>
<a-input v-model.trim="n.delay" placeholder="10-20" />
</a-form-item>
</template>
</template>
<template v-if="mask.type === 'sudoku'">
<a-form-item label='ASCII'>
<a-input v-model.trim="mask.settings.ascii" placeholder="ASCII" />
</a-form-item>
<a-form-item label='Custom Table'>
<a-input v-model.trim="mask.settings.customTable" placeholder="Custom Table" />
</a-form-item>
<a-form-item label='Custom Tables'>
<a-input v-model.trim="mask.settings.customTables" placeholder="Custom Tables" />
</a-form-item>
<a-form-item label='Padding Min'>
<a-input-number v-model.number="mask.settings.paddingMin" :min="0" />
</a-form-item>
<a-form-item label='Padding Max'>
<a-input-number v-model.number="mask.settings.paddingMax" :min="0" />
</a-form-item>
</template>
<template v-if="mask.type === 'xicmp'">
<a-form-item label='IP'>
<a-input v-model.trim="mask.settings.ip" placeholder="0.0.0.0" />
</a-form-item>
<a-form-item label='ID'>
<a-input-number v-model.number="mask.settings.id" :min="0" />
</a-form-item>
</template>
</a-form> </a-form>
</template> </template>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -10,4 +10,4 @@
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch> <a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -14,13 +14,16 @@
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
<a-input :style="{ width: '50%' }" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'> <a-input :style="{ width: '50%' }" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template> <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input :style="{ width: '50%' }" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input :style="{ width: '50%' }" v-model.trim="header.value"
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.httpupgrade.removeHeader(index)"></a-button> placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button icon="minus" slot="addonAfter" size="small"
@click="inbound.stream.httpupgrade.removeHeader(index)"></a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -1,12 +1,10 @@
{{define "form/streamHysteria"}} {{define "form/streamHysteria"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }">
<a-form-item label='Auth Password'> <a-form-item label='Auth Password'>
<a-input v-model.trim="inbound.stream.hysteria.auth"></a-input> <a-input v-model.trim="inbound.stream.hysteria.auth"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='UDP Idle Timeout'> <a-form-item label='UDP Idle Timeout'>
<a-input-number v-model.number="inbound.stream.hysteria.udpIdleTimeout" <a-input-number v-model.number="inbound.stream.hysteria.udpIdleTimeout" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Masquerade'> <a-form-item label='Masquerade'>
<a-switch v-model="inbound.stream.hysteria.masqueradeSwitch"></a-switch> <a-switch v-model="inbound.stream.hysteria.masqueradeSwitch"></a-switch>
@ -21,42 +19,32 @@
<a-select-option value="string">String</a-select-option> <a-select-option value="string">String</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Dir' <a-form-item label='Dir' v-if="inbound.stream.hysteria.masquerade.type === 'file'">
v-if="inbound.stream.hysteria.masquerade.type === 'file'"> <a-input v-model.trim="inbound.stream.hysteria.masquerade.dir"></a-input>
<a-input
v-model.trim="inbound.stream.hysteria.masquerade.dir"></a-input>
</a-form-item> </a-form-item>
<template v-if="inbound.stream.hysteria.masquerade.type === 'proxy'"> <template v-if="inbound.stream.hysteria.masquerade.type === 'proxy'">
<a-form-item label='URL'> <a-form-item label='URL'>
<a-input <a-input v-model.trim="inbound.stream.hysteria.masquerade.url"></a-input>
v-model.trim="inbound.stream.hysteria.masquerade.url"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Rewrite Host'> <a-form-item label='Rewrite Host'>
<a-switch <a-switch v-model="inbound.stream.hysteria.masquerade.rewriteHost"></a-switch>
v-model="inbound.stream.hysteria.masquerade.rewriteHost"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='Insecure'> <a-form-item label='Insecure'>
<a-switch <a-switch v-model="inbound.stream.hysteria.masquerade.insecure"></a-switch>
v-model="inbound.stream.hysteria.masquerade.insecure"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
<template v-if="inbound.stream.hysteria.masquerade.type === 'string'"> <template v-if="inbound.stream.hysteria.masquerade.type === 'string'">
<a-form-item label='Content'> <a-form-item label='Content'>
<a-input <a-input v-model.trim="inbound.stream.hysteria.masquerade.content"></a-input>
v-model.trim="inbound.stream.hysteria.masquerade.content"></a-input>
</a-form-item> </a-form-item>
<a-form-item <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-button size="small" @click="inbound.stream.hysteria.masquerade.addHeader('', '')">+</a-button>
<a-button size="small"
@click="inbound.stream.hysteria.masquerade.addHeader('', '')">+</a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact <a-input-group compact v-for="(header, index) in inbound.stream.hysteria.masquerade.headers">
v-for="(header, index) in inbound.stream.hysteria.masquerade.headers">
<a-input style="width: 50%" v-model.trim="header.name" <a-input style="width: 50%" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'> placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" style="margin: 0;">[[ <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" <a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
@ -66,8 +54,7 @@
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item label='Status Code'> <a-form-item label='Status Code'>
<a-input-number <a-input-number v-model.number="inbound.stream.hysteria.masquerade.statusCode"></a-input-number>
v-model.number="inbound.stream.hysteria.masquerade.statusCode"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>

View file

@ -1,32 +1,25 @@
{{define "form/streamKCP"}} {{define "form/streamKCP"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }">
<a-form-item label='MTU'> <a-form-item label='MTU'>
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576" <a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576" :max="1460"></a-input-number>
:max="1460"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='TTI (ms)'> <a-form-item label='TTI (ms)'>
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10" <a-input-number v-model.number="inbound.stream.kcp.tti" :min="10" :max="100"></a-input-number>
:max="100"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Uplink (MB/s)'> <a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.upCap" <a-input-number v-model.number="inbound.stream.kcp.upCap" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Downlink (MB/s)'> <a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.downCap" <a-input-number v-model.number="inbound.stream.kcp.downCap" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Congestion'> <a-form-item label='Congestion'>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch> <a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='Read Buffer (MB)'> <a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer" <a-input-number v-model.number="inbound.stream.kcp.readBuffer" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Write Buffer (MB)'> <a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer" <a-input-number v-model.number="inbound.stream.kcp.writeBuffer" :min="0"></a-input-number>
:min="0"></a-input-number>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -1,65 +1,62 @@
{{define "form/streamSettings"}} {{define "form/streamSettings"}}
<!-- select stream network --> <!-- select stream network -->
<a-form :colon="false" :label-col="{ md: {span:8} }" <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"
:wrapper-col="{ md: {span:14} }" v-if="inbound.protocol != Protocols.HYSTERIA">
v-if="inbound.protocol != Protocols.HYSTERIA"> <a-form-item label='{{ i18n "transmission" }}'>
<a-form-item label='{{ i18n "transmission" }}'> <a-select v-model="inbound.stream.network" :style="{ width: '75%' }" @change="streamNetworkChange"
<a-select v-model="inbound.stream.network" :style="{ width: '75%' }" :dropdown-class-name="themeSwitcher.currentTheme">
@change="streamNetworkChange" <a-select-option value="tcp">TCP (RAW)</a-select-option>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="tcp">TCP (RAW)</a-select-option> <a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option> <a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option> <a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option> <a-select-option value="xhttp">XHTTP</a-select-option>
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option> </a-select>
<a-select-option value="xhttp">XHTTP</a-select-option> </a-form-item>
</a-select>
</a-form-item>
</a-form> </a-form>
<!-- tcp --> <!-- tcp -->
<template v-if="inbound.stream.network === 'tcp'"> <template v-if="inbound.stream.network === 'tcp'">
{{template "form/streamTCP"}} {{template "form/streamTCP"}}
</template> </template>
<!-- kcp --> <!-- kcp -->
<template v-if="inbound.stream.network === 'kcp'"> <template v-if="inbound.stream.network === 'kcp'">
{{template "form/streamKCP"}} {{template "form/streamKCP"}}
</template> </template>
<!-- ws --> <!-- ws -->
<template v-if="inbound.stream.network === 'ws'"> <template v-if="inbound.stream.network === 'ws'">
{{template "form/streamWS"}} {{template "form/streamWS"}}
</template> </template>
<!-- grpc --> <!-- grpc -->
<template v-if="inbound.stream.network === 'grpc'"> <template v-if="inbound.stream.network === 'grpc'">
{{template "form/streamGRPC"}} {{template "form/streamGRPC"}}
</template> </template>
<!-- hysteria --> <!-- hysteria -->
<template v-if="inbound.stream.network === 'hysteria'"> <template v-if="inbound.stream.network === 'hysteria'">
{{template "form/streamHysteria"}} {{template "form/streamHysteria"}}
</template> </template>
<!-- httpupgrade --> <!-- httpupgrade -->
<template v-if="inbound.stream.network === 'httpupgrade'"> <template v-if="inbound.stream.network === 'httpupgrade'">
{{template "form/streamHTTPUpgrade"}} {{template "form/streamHTTPUpgrade"}}
</template> </template>
<!-- xhttp --> <!-- xhttp -->
<template v-if="inbound.stream.network === 'xhttp'"> <template v-if="inbound.stream.network === 'xhttp'">
{{template "form/streamXHTTP"}} {{template "form/streamXHTTP"}}
</template> </template>
<!-- sockopt --> <!-- sockopt -->
<template> <template>
{{template "form/streamSockopt"}} {{template "form/streamSockopt"}}
</template> </template>
<!-- finalmask - only for TCP, WS, HTTPUpgrade, XHTTP, mKCP --> <!-- finalmask -->
<template <template>
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(inbound.stream.network)"> {{template "form/streamFinalMask"}}
{{template "form/streamFinalMask"}}
</template> </template>
{{end}} {{end}}

View file

@ -39,17 +39,20 @@
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch> <a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='Domain Strategy'> <a-form-item label='Domain Strategy'>
<a-select v-model="inbound.stream.sockopt.domainStrategy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.stream.sockopt.domainStrategy" :style="{ width: '50%' }"
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='TCP Congestion'> <a-form-item label='TCP Congestion'>
<a-select v-model="inbound.stream.sockopt.tcpcongestion" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.stream.sockopt.tcpcongestion" :style="{ width: '50%' }"
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="TProxy"> <a-form-item label="TProxy">
<a-select v-model="inbound.stream.sockopt.tproxy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.stream.sockopt.tproxy" :style="{ width: '50%' }"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="off">Off</a-select-option> <a-select-option value="off">Off</a-select-option>
<a-select-option value="redirect">Redirect</a-select-option> <a-select-option value="redirect">Redirect</a-select-option>
<a-select-option value="tproxy">TProxy</a-select-option> <a-select-option value="tproxy">TProxy</a-select-option>
@ -72,4 +75,4 @@
</a-form-item> </a-form-item>
</template> </template>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -5,11 +5,13 @@
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch> <a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='HTTP {{ i18n "camouflage" }}'> <a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch :checked="inbound.stream.tcp.type === 'http'" @change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'"></a-switch> <a-switch :checked="inbound.stream.tcp.type === 'http'"
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'"></a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<!-- tcp request --> <!-- tcp request -->
<a-divider :style="{ margin: '0' }">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider> <a-divider :style="{ margin: '0' }">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
@ -24,7 +26,8 @@
</template> </template>
<template v-for="(path, index) in inbound.stream.tcp.request.path"> <template v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"> <a-input v-model.trim="inbound.stream.tcp.request.path[index]">
<a-button icon="minus" size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)" v-if="inbound.stream.tcp.request.path.length>1"></a-button> <a-button icon="minus" size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)"
v-if="inbound.stream.tcp.request.path.length>1"></a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>
@ -33,11 +36,14 @@
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
<a-input :style="{ width: '50%' }" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <a-input :style="{ width: '50%' }" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template> <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input :style="{ width: '50%' }" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input :style="{ width: '50%' }" v-model.trim="header.value"
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)"></a-button> placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button icon="minus" slot="addonAfter" size="small"
@click="inbound.stream.tcp.request.removeHeader(index)"></a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
@ -54,14 +60,17 @@
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input> <a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
<a-button icon="plus" size="small" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"></a-button> <a-button icon="plus" size="small"
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"></a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input :style="{ width: '50%' }" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <a-input :style="{ width: '50%' }" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template> <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input :style="{ width: '50%' }" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input :style="{ width: '50%' }" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter"> <template slot="addonAfter">
<a-button icon="minus" size="small" @click="inbound.stream.tcp.response.removeHeader(index)"></a-button> <a-button icon="minus" size="small" @click="inbound.stream.tcp.response.removeHeader(index)"></a-button>
</template> </template>
@ -69,4 +78,4 @@
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -17,13 +17,15 @@
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
<a-input :style="{ width: '50%' }" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'> <a-input :style="{ width: '50%' }" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template> <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input :style="{ width: '50%' }" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input :style="{ width: '50%' }" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)"></a-button> <a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)"></a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -1,6 +1,5 @@
{{define "form/streamXHTTP"}} {{define "form/streamXHTTP"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="inbound.stream.xhttp.host"></a-input> <a-input v-model.trim="inbound.stream.xhttp.host"></a-input>
</a-form-item> </a-form-item>
@ -8,12 +7,10 @@
<a-input v-model.trim="inbound.stream.xhttp.path"></a-input> <a-input v-model.trim="inbound.stream.xhttp.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button icon="plus" size="small" <a-button icon="plus" size="small" @click="inbound.stream.xhttp.addHeader('', '')"></a-button>
@click="inbound.stream.xhttp.addHeader('', '')"></a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact <a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
v-for="(header, index) in inbound.stream.xhttp.headers">
<a-input :style="{ width: '50%' }" v-model.trim="header.name" <a-input :style="{ width: '50%' }" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'> placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1
@ -33,20 +30,14 @@
]]</a-select-option> ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Max Buffered Upload" <a-form-item label="Max Buffered Upload" v-if="inbound.stream.xhttp.mode === 'packet-up'">
v-if="inbound.stream.xhttp.mode === 'packet-up'"> <a-input-number v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
<a-input-number
v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="Max Upload Size (Byte)" <a-form-item label="Max Upload Size (Byte)" v-if="inbound.stream.xhttp.mode === 'packet-up'">
v-if="inbound.stream.xhttp.mode === 'packet-up'"> <a-input v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
<a-input
v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Stream-Up Server" <a-form-item label="Stream-Up Server" v-if="inbound.stream.xhttp.mode === 'stream-up'">
v-if="inbound.stream.xhttp.mode === 'stream-up'"> <a-input v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
<a-input
v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Padding Bytes"> <a-form-item label="Padding Bytes">
<a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input> <a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
@ -56,27 +47,23 @@
</a-form-item> </a-form-item>
<template v-if="inbound.stream.xhttp.xPaddingObfsMode"> <template v-if="inbound.stream.xhttp.xPaddingObfsMode">
<a-form-item label="Padding Key"> <a-form-item label="Padding Key">
<a-input v-model.trim="inbound.stream.xhttp.xPaddingKey" <a-input v-model.trim="inbound.stream.xhttp.xPaddingKey" placeholder="x_padding"></a-input>
placeholder="x_padding"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Padding Header"> <a-form-item label="Padding Header">
<a-input v-model.trim="inbound.stream.xhttp.xPaddingHeader" <a-input v-model.trim="inbound.stream.xhttp.xPaddingHeader" placeholder="X-Padding"></a-input>
placeholder="X-Padding"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Padding Placement"> <a-form-item label="Padding Placement">
<a-select v-model="inbound.stream.xhttp.xPaddingPlacement" <a-select v-model="inbound.stream.xhttp.xPaddingPlacement"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (queryInHeader)</a-select-option> <a-select-option value>Default (queryInHeader)</a-select-option>
<a-select-option <a-select-option value="queryInHeader">queryInHeader</a-select-option>
value="queryInHeader">queryInHeader</a-select-option>
<a-select-option value="header">header</a-select-option> <a-select-option value="header">header</a-select-option>
<a-select-option value="cookie">cookie</a-select-option> <a-select-option value="cookie">cookie</a-select-option>
<a-select-option value="query">query</a-select-option> <a-select-option value="query">query</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Padding Method"> <a-form-item label="Padding Method">
<a-select v-model="inbound.stream.xhttp.xPaddingMethod" <a-select v-model="inbound.stream.xhttp.xPaddingMethod" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (repeat-x)</a-select-option> <a-select-option value>Default (repeat-x)</a-select-option>
<a-select-option value="repeat-x">repeat-x</a-select-option> <a-select-option value="repeat-x">repeat-x</a-select-option>
<a-select-option value="tokenish">tokenish</a-select-option> <a-select-option value="tokenish">tokenish</a-select-option>
@ -84,8 +71,7 @@
</a-form-item> </a-form-item>
</template> </template>
<a-form-item label="Uplink HTTP Method"> <a-form-item label="Uplink HTTP Method">
<a-select v-model="inbound.stream.xhttp.uplinkHTTPMethod" <a-select v-model="inbound.stream.xhttp.uplinkHTTPMethod" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (POST)</a-select-option> <a-select-option value>Default (POST)</a-select-option>
<a-select-option value="POST">POST</a-select-option> <a-select-option value="POST">POST</a-select-option>
<a-select-option value="PUT">PUT</a-select-option> <a-select-option value="PUT">PUT</a-select-option>
@ -93,8 +79,7 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Session Placement"> <a-form-item label="Session Placement">
<a-select v-model="inbound.stream.xhttp.sessionPlacement" <a-select v-model="inbound.stream.xhttp.sessionPlacement" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (path)</a-select-option> <a-select-option value>Default (path)</a-select-option>
<a-select-option value="path">path</a-select-option> <a-select-option value="path">path</a-select-option>
<a-select-option value="header">header</a-select-option> <a-select-option value="header">header</a-select-option>
@ -104,12 +89,10 @@
</a-form-item> </a-form-item>
<a-form-item label="Session Key" <a-form-item label="Session Key"
v-if="inbound.stream.xhttp.sessionPlacement && inbound.stream.xhttp.sessionPlacement !== 'path'"> v-if="inbound.stream.xhttp.sessionPlacement && inbound.stream.xhttp.sessionPlacement !== 'path'">
<a-input v-model.trim="inbound.stream.xhttp.sessionKey" <a-input v-model.trim="inbound.stream.xhttp.sessionKey" placeholder="x_session"></a-input>
placeholder="x_session"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Sequence Placement"> <a-form-item label="Sequence Placement">
<a-select v-model="inbound.stream.xhttp.seqPlacement" <a-select v-model="inbound.stream.xhttp.seqPlacement" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (path)</a-select-option> <a-select-option value>Default (path)</a-select-option>
<a-select-option value="path">path</a-select-option> <a-select-option value="path">path</a-select-option>
<a-select-option value="header">header</a-select-option> <a-select-option value="header">header</a-select-option>
@ -119,13 +102,10 @@
</a-form-item> </a-form-item>
<a-form-item label="Sequence Key" <a-form-item label="Sequence Key"
v-if="inbound.stream.xhttp.seqPlacement && inbound.stream.xhttp.seqPlacement !== 'path'"> v-if="inbound.stream.xhttp.seqPlacement && inbound.stream.xhttp.seqPlacement !== 'path'">
<a-input v-model.trim="inbound.stream.xhttp.seqKey" <a-input v-model.trim="inbound.stream.xhttp.seqKey" placeholder="x_seq"></a-input>
placeholder="x_seq"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Uplink Data Placement" <a-form-item label="Uplink Data Placement" v-if="inbound.stream.xhttp.mode === 'packet-up'">
v-if="inbound.stream.xhttp.mode === 'packet-up'"> <a-select v-model="inbound.stream.xhttp.uplinkDataPlacement" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select v-model="inbound.stream.xhttp.uplinkDataPlacement"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (body)</a-select-option> <a-select-option value>Default (body)</a-select-option>
<a-select-option value="body">body</a-select-option> <a-select-option value="body">body</a-select-option>
<a-select-option value="header">header</a-select-option> <a-select-option value="header">header</a-select-option>
@ -135,13 +115,12 @@
</a-form-item> </a-form-item>
<a-form-item label="Uplink Data Key" <a-form-item label="Uplink Data Key"
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'"> v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
<a-input v-model.trim="inbound.stream.xhttp.uplinkDataKey" <a-input v-model.trim="inbound.stream.xhttp.uplinkDataKey" placeholder="x_data"></a-input>
placeholder="x_data"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Uplink Chunk Size" <a-form-item label="Uplink Chunk Size"
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'"> v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
<a-input-number v-model.number="inbound.stream.xhttp.uplinkChunkSize" <a-input-number v-model.number="inbound.stream.xhttp.uplinkChunkSize" :min="0"
:min="0" placeholder="0 (unlimited)"></a-input-number> placeholder="0 (unlimited)"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="No SSE Header"> <a-form-item label="No SSE Header">
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch> <a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>

View file

@ -1,8 +1,6 @@
{{define "modals/inboundInfoModal"}} {{define "modals/inboundInfoModal"}}
<a-modal id="inbound-info-modal" v-model="infoModal.visible" <a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' :closable="true"
title='{{ i18n "pages.inbounds.details"}}' :closable="true" :mask-closable="true" :footer="null" width="600px" :class="themeSwitcher.currentTheme">
:mask-closable="true" :footer="null" width="600px"
:class="themeSwitcher.currentTheme">
<a-row> <a-row>
<a-col :xs="24" :md="12"> <a-col :xs="24" :md="12">
<table> <table>
@ -29,8 +27,7 @@
</table> </table>
</a-col> </a-col>
<a-col :xs="24" :md="12"> <a-col :xs="24" :md="12">
<template <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<table> <table>
<tr> <tr>
<td>{{ i18n "transmission" }}</td> <td>{{ i18n "transmission" }}</td>
@ -38,8 +35,7 @@
<a-tag color="green">[[ inbound.network ]]</a-tag> <a-tag color="green">[[ inbound.network ]]</a-tag>
</td> </td>
</tr> </tr>
<template <template v-if="inbound.isTcp || inbound.isWs || inbound.isHttpupgrade || inbound.isXHTTP">
v-if="inbound.isTcp || inbound.isWs || inbound.isHttpupgrade || inbound.isXHTTP">
<tr> <tr>
<td>{{ i18n "host" }}</td> <td>{{ i18n "host" }}</td>
<td v-if="inbound.host"> <td v-if="inbound.host">
@ -51,13 +47,13 @@
<a-tag color="orange">{{ i18n "none" }}</a-tag> <a-tag color="orange">{{ i18n "none" }}</a-tag>
</td> </td>
</tr> </tr>
</tr> </tr>
<tr> <tr>
<td>{{ i18n "path" }}</td> <td>{{ i18n "path" }}</td>
<td v-if="inbound.path"> <td v-if="inbound.path">
<a-tooltip :title="[[ inbound.path ]]"> <a-tooltip :title="[[ inbound.path ]]">
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag> <a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
</a-tooltip> </a-tooltip>
<td v-else> <td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag> <a-tag color="orange">{{ i18n "none" }}</a-tag>
</td> </td>
@ -79,483 +75,452 @@
<a-tag class="info-large-tag">[[ inbound.serviceName <a-tag class="info-large-tag">[[ inbound.serviceName
]]</a-tag> ]]</a-tag>
</a-tooltip> </a-tooltip>
<tr> <tr>
<td>grpc multiMode</td> <td>grpc multiMode</td>
<td> <td>
<a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag> <a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag>
</td> </td>
</tr> </tr>
</template>
</table>
</template> </template>
</a-col>
<template v-if="dbInbound.hasLink()">
{{ i18n "security" }}
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[
inbound.stream.security ]]</a-tag>
<br />
<td>Authentication</td>
<a-tag v-if="inbound.settings.selectedAuth" color="green">[[
inbound.settings.selectedAuth ? inbound.settings.selectedAuth : ''
]]</a-tag>
<a-tag v-else color="red">{{ i18n "none" }}</a-tag>
<br />
{{ i18n "encryption" }}
<a-tag class="info-large-tag"
:color="inbound.settings.encryption ? 'green' : 'red'">[[
inbound.settings.encryption ? inbound.settings.encryption : ''
]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets"
@click="copy(inbound.settings.encryption)"></a-button>
</a-tooltip>
<br />
<template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }}
<a-tag v-if="inbound.serverName" color="green">[[ inbound.serverName
? inbound.serverName : '' ]]</a-tag>
<a-tag v-else color="orange">{{ i18n "none" }}</a-tag>
</template>
</template>
<table v-if="dbInbound.isSS"
:style="{ marginBottom: '10px', width: '100%' }">
<tr>
<td>{{ i18n "encryption" }}</td>
<td>
<a-tag color="green">[[ inbound.settings.method ]]</a-tag>
</td>
</tr>
<tr v-if="inbound.isSS2022">
<td>{{ i18n "password" }}</td>
<td>
<a-tooltip :title="[[ inbound.settings.password ]]">
<a-tag class="info-large-tag">[[ inbound.settings.password
]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td>
<a-tag color="green">[[ inbound.settings.network ]]</a-tag>
</td>
</tr>
</table> </table>
<template v-if="infoModal.clientSettings"> </template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider> </a-col>
<table :style="{ marginBottom: '10px' }"> <template v-if="dbInbound.hasLink()">
<tr> {{ i18n "security" }}
<td>{{ i18n "pages.inbounds.email" }}</td> <a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[
<td v-if="infoModal.clientSettings.email"> inbound.stream.security ]]</a-tag>
<a-tag color="green">[[ infoModal.clientSettings.email <br />
]]</a-tag> <td>Authentication</td>
</td> <a-tag v-if="inbound.settings.selectedAuth" color="green">[[
<td v-else> inbound.settings.selectedAuth ? inbound.settings.selectedAuth : ''
<a-tag color="red">{{ i18n "none" }}</a-tag> ]]</a-tag>
</td> <a-tag v-else color="red">{{ i18n "none" }}</a-tag>
</tr> <br />
<tr v-if="infoModal.clientSettings.id"> {{ i18n "encryption" }}
<td>ID</td> <a-tag class="info-large-tag" :color="inbound.settings.encryption ? 'green' : 'red'">[[
<td> inbound.settings.encryption ? inbound.settings.encryption : ''
<a-tag>[[ infoModal.clientSettings.id ]]</a-tag> ]]</a-tag>
</td> <a-tooltip title='{{ i18n "copy" }}'>
</tr> <a-button size="small" icon="snippets" @click="copy(inbound.settings.encryption)"></a-button>
<tr v-if="dbInbound.isVMess"> </a-tooltip>
<td>{{ i18n "security" }}</td> <br />
<td> <template v-if="inbound.stream.security != 'none'">
<a-tag>[[ infoModal.clientSettings.security ]]</a-tag> {{ i18n "domainName" }}
</td> <a-tag v-if="inbound.serverName" color="green">[[ inbound.serverName
</tr> ? inbound.serverName : '' ]]</a-tag>
<tr v-if="infoModal.inbound.canEnableTlsFlow()"> <a-tag v-else color="orange">{{ i18n "none" }}</a-tag>
<td>Flow</td> </template>
<td v-if="infoModal.clientSettings.flow"> </template>
<a-tag>[[ infoModal.clientSettings.flow ]]</a-tag> <table v-if="dbInbound.isSS" :style="{ marginBottom: '10px', width: '100%' }">
</td> <tr>
<td v-else> <td>{{ i18n "encryption" }}</td>
<a-tag color="orange">{{ i18n "none" }}</a-tag> <td>
</td> <a-tag color="green">[[ inbound.settings.method ]]</a-tag>
</tr> </td>
<tr v-if="infoModal.clientSettings.password"> </tr>
<td>{{ i18n "password" }}</td> <tr v-if="inbound.isSS2022">
<td> <td>{{ i18n "password" }}</td>
<a-tooltip :title="[[ infoModal.clientSettings.password ]]"> <td>
<a-tag class="info-large-tag">[[ <a-tooltip :title="[[ inbound.settings.password ]]">
infoModal.clientSettings.password ]]</a-tag> <a-tag class="info-large-tag">[[ inbound.settings.password
</a-tooltip> ]]</a-tag>
</td> </a-tooltip>
</tr> </td>
<tr> </tr>
<td>{{ i18n "status" }}</td> <tr>
<td> <td>{{ i18n "pages.inbounds.network" }}</td>
<a-tag v-if="isDepleted" color="red">{{ i18n "depleted" <td>
}}</a-tag> <a-tag color="green">[[ inbound.settings.network ]]</a-tag>
<a-tag v-else-if="isEnable" color="green">{{ i18n "enabled" </td>
}}</a-tag> </tr>
<a-tag v-else>{{ i18n "disabled" }}</a-tag> </table>
</td> <template v-if="infoModal.clientSettings">
</tr> <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<tr v-if="infoModal.clientStats"> <table :style="{ marginBottom: '10px' }">
<td>{{ i18n "usage" }}</td> <tr>
<td> <td>{{ i18n "pages.inbounds.email" }}</td>
<a-tag color="green">[[ <td v-if="infoModal.clientSettings.email">
SizeFormatter.sizeFormat(infoModal.clientStats.up + <a-tag color="green">[[ infoModal.clientSettings.email
infoModal.clientStats.down) ]]</a-tag> ]]</a-tag>
<a-tag>↑ [[ SizeFormatter.sizeFormat(infoModal.clientStats.up) </td>
]] / [[ SizeFormatter.sizeFormat(infoModal.clientStats.down) <td v-else>
]] ↓</a-tag> <a-tag color="red">{{ i18n "none" }}</a-tag>
</td> </td>
</tr> </tr>
<tr> <tr v-if="infoModal.clientSettings.id">
<td>{{ i18n "pages.inbounds.createdAt" }}</td> <td>ID</td>
<td> <td>
<template <a-tag>[[ infoModal.clientSettings.id ]]</a-tag>
v-if="infoModal.clientSettings && infoModal.clientSettings.created_at"> </td>
<a-tag>[[ </tr>
IntlUtil.formatDate(infoModal.clientSettings.created_at) <tr v-if="dbInbound.isVMess">
]]</a-tag> <td>{{ i18n "security" }}</td>
</template> <td>
<template v-else> <a-tag>[[ infoModal.clientSettings.security ]]</a-tag>
<a-tag>-</a-tag> </td>
</template> </tr>
</td> <tr v-if="infoModal.inbound.canEnableTlsFlow()">
</tr> <td>Flow</td>
<tr> <td v-if="infoModal.clientSettings.flow">
<td>{{ i18n "pages.inbounds.updatedAt" }}</td> <a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
<td> </td>
<template <td v-else>
v-if="infoModal.clientSettings && infoModal.clientSettings.updated_at"> <a-tag color="orange">{{ i18n "none" }}</a-tag>
<a-tag>[[ </td>
IntlUtil.formatDate(infoModal.clientSettings.updated_at) </tr>
]]</a-tag> <tr v-if="infoModal.clientSettings.password">
</template> <td>{{ i18n "password" }}</td>
<template v-else> <td>
<a-tag>-</a-tag> <a-tooltip :title="[[ infoModal.clientSettings.password ]]">
</template> <a-tag class="info-large-tag">[[
</td> infoModal.clientSettings.password ]]</a-tag>
</tr> </a-tooltip>
<tr> </td>
<td>{{ i18n "lastOnline" }}</td> </tr>
<td> <tr>
<a-tag>[[ app.formatLastOnline(infoModal.clientSettings && <td>{{ i18n "status" }}</td>
infoModal.clientSettings.email ? <td>
infoModal.clientSettings.email : '') ]]</a-tag> <a-tag v-if="isDepleted" color="red">{{ i18n "depleted"
</td> }}</a-tag>
</tr> <a-tag v-else-if="isEnable" color="green">{{ i18n "enabled"
<tr v-if="infoModal.clientSettings.comment"> }}</a-tag>
<td>{{ i18n "comment" }}</td> <a-tag v-else>{{ i18n "disabled" }}</a-tag>
<td> </td>
<a-tooltip :title="[[ infoModal.clientSettings.comment ]]"> </tr>
<a-tag class="info-large-tag">[[ <tr v-if="infoModal.clientStats">
infoModal.clientSettings.comment ]]</a-tag> <td>{{ i18n "usage" }}</td>
</a-tooltip> <td>
</td> <a-tag color="green">[[
</tr> SizeFormatter.sizeFormat(infoModal.clientStats.up +
<tr v-if="app.ipLimitEnable"> infoModal.clientStats.down) ]]</a-tag>
<td>{{ i18n "pages.inbounds.IPLimit" }}</td> <a-tag>↑ [[ SizeFormatter.sizeFormat(infoModal.clientStats.up)
<td> ]] / [[ SizeFormatter.sizeFormat(infoModal.clientStats.down)
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag> ]] ↓</a-tag>
</td> </td>
</tr> </tr>
<tr <tr>
v-if="app.ipLimitEnable && infoModal.clientSettings.limitIp > 0"> <td>{{ i18n "pages.inbounds.createdAt" }}</td>
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td> <td>
<td> <template v-if="infoModal.clientSettings && infoModal.clientSettings.created_at">
<div <a-tag>[[
style="max-height: 150px; overflow-y: auto; text-align: left;"> IntlUtil.formatDate(infoModal.clientSettings.created_at)
<div ]]</a-tag>
v-if="infoModal.clientIpsArray && infoModal.clientIpsArray.length > 0">
<a-tag
v-for="(ipInfo, idx) in infoModal.clientIpsArray"
:key="idx"
color="blue"
style="margin: 2px 0; display: block; font-family: monospace; font-size: 11px;">
[[ formatIpInfo(ipInfo) ]]
</a-tag>
</div>
<a-tag v-else>[[ infoModal.clientIps || 'No IP Record'
]]</a-tag>
</div>
<div style="margin-top: 5px;">
<a-icon type="sync" :spin="refreshing" @click="refreshIPs"
:style="{ margin: '0 5px' }"></a-icon>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
</template>
<a-icon type="delete" @click="clearClientIps"></a-icon>
</a-tooltip>
</div>
</td>
</tr>
</table>
<table
:style="{ display: 'inline-table', marginBlock: '10px', width: '100%', textAlign: 'center' }">
<tr>
<th>{{ i18n "remained" }}</th>
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
</tr>
<tr>
<td>
<a-tag
v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0"
:color="statsColor(infoModal.clientStats)"> [[ getRemStats()
]] </a-tag>
</td>
<td>
<a-tag v-if="infoModal.clientSettings.totalGB > 0"
:color="statsColor(infoModal.clientStats)"> [[
SizeFormatter.sizeFormat(infoModal.clientSettings.totalGB) ]]
</a-tag>
<a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512"
fill="currentColor">
<path
d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"
fill="currentColor"></path>
</svg>
</a-tag>
</td>
<td>
<template v-if="infoModal.clientSettings.expiryTime > 0">
<a-tag
:color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
[[ IntlUtil.formatDate(infoModal.clientSettings.expiryTime)
]]
</a-tag>
</template>
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0"
color="green">[[ infoModal.clientSettings.expiryTime /
-86400000 ]] {{ i18n "pages.client.days" }}
</a-tag>
<a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512"
fill="currentColor">
<path
d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"
fill="currentColor"></path>
</svg>
</a-tag>
</td>
</tr>
</table>
<template
v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription URL</a-divider>
<tr-info-row class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag color="purple">Subscription Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets"
@click="copy(infoModal.subLink)"></a-button>
</a-tooltip>
</tr-info-title>
<a :href="[[ infoModal.subLink ]]" target="_blank">[[
infoModal.subLink ]]</a>
</tr-info-row>
<tr-info-row class="tr-info-row"
v-if="app.subSettings.subJsonEnable">
<tr-info-title class="tr-info-title">
<a-tag color="purple">Json Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets"
@click="copy(infoModal.subJsonLink)"></a-button>
</a-tooltip>
</tr-info-title>
<a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[
infoModal.subJsonLink ]]</a>
</tr-info-row>
</template>
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram ChatID</a-divider>
<tr-info-row class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets"
@click="copy(infoModal.clientSettings.tgId)"></a-button>
</a-tooltip>
</tr-info-title>
</tr-info-row>
</template>
<template v-if="dbInbound.hasLink()">
<a-divider>URL</a-divider>
<tr-info-row v-for="(link,index) in infoModal.links"
class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark
]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button :style="{ minWidth: '24px' }" size="small"
icon="snippets" @click="copy(link.link)"></a-button>
</a-tooltip>
</tr-info-title>
<code>[[ link.link ]]</code>
</tr-info-row>
</template>
</template>
<template v-else>
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
<a-divider>URL</a-divider>
<tr-info-row v-for="(link,index) in infoModal.links"
class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark
]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button :style="{ minWidth: '24px' }" size="small"
icon="snippets" @click="copy(link.link)"></a-button>
</a-tooltip>
</tr-info-title>
<code>[[ link.link ]]</code>
</tr-info-row>
</template>
<table v-if="inbound.protocol == Protocols.TUNNEL"
class="tr-info-table">
<tr>
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
<th>{{ i18n "pages.inbounds.network" }}</th>
<th>FollowRedirect</th>
</tr>
<tr>
<td>
<a-tag color="green">[[ inbound.settings.address ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.port ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.network ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.followRedirect
]]</a-tag>
</td>
</tr>
</table>
<table v-if="dbInbound.isMixed" class="tr-info-table">
<tr>
<th>{{ i18n "password" }} Auth</th>
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
<th>IP</th>
</tr>
<tr>
<td>
<a-tag color="green">[[ inbound.settings.auth ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.udp]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.ip ]]</a-tag>
</td>
</tr>
<template v-if="inbound.settings.auth == 'password'">
<tr>
<td></td>
<td>{{ i18n "username" }}</td>
<td>{{ i18n "password" }}</td>
</tr>
<tr v-for="account,index in inbound.settings.accounts">
<td>[[ index ]]</td>
<td>
<a-tag color="green">[[ account.user ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ account.pass ]]</a-tag>
</td>
</tr>
</template> </template>
</table> <template v-else>
<table v-if="dbInbound.isHTTP" class="tr-info-table"> <a-tag>-</a-tag>
<tr> </template>
<th></th> </td>
<th>{{ i18n "username" }}</th> </tr>
<th>{{ i18n "password" }}</th> <tr>
</tr> <td>{{ i18n "pages.inbounds.updatedAt" }}</td>
<tr v-for="account,index in inbound.settings.accounts"> <td>
<td>[[ index ]]</td> <template v-if="infoModal.clientSettings && infoModal.clientSettings.updated_at">
<td> <a-tag>[[
<a-tag color="green">[[ account.user ]]</a-tag> IntlUtil.formatDate(infoModal.clientSettings.updated_at)
</td> ]]</a-tag>
<td> </template>
<a-tag color="green">[[ account.pass ]]</a-tag> <template v-else>
</td> <a-tag>-</a-tag>
</tr> </template>
</table> </td>
<table v-if="dbInbound.isWireguard" class="tr-info-table"> </tr>
<tr class="client-table-odd-row"> <tr>
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td> <td>{{ i18n "lastOnline" }}</td>
<td>[[ inbound.settings.secretKey ]]</td> <td>
</tr> <a-tag>[[ app.formatLastOnline(infoModal.clientSettings &&
<tr> infoModal.clientSettings.email ?
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td> infoModal.clientSettings.email : '') ]]</a-tag>
<td>[[ inbound.settings.pubKey ]]</td> </td>
</tr> </tr>
<tr class="client-table-odd-row"> <tr v-if="infoModal.clientSettings.comment">
<td>MTU</td> <td>{{ i18n "comment" }}</td>
<td>[[ inbound.settings.mtu ]]</td> <td>
</tr> <a-tooltip :title="[[ infoModal.clientSettings.comment ]]">
<tr> <a-tag class="info-large-tag">[[
<td>No Kernel Tun</td> infoModal.clientSettings.comment ]]</a-tag>
<td>[[ inbound.settings.noKernelTun ]]</td> </a-tooltip>
</tr> </td>
<template v-for="(peer, index) in inbound.settings.peers"> </tr>
<tr> <tr v-if="app.ipLimitEnable">
<td colspan="2"> <td>{{ i18n "pages.inbounds.IPLimit" }}</td>
<a-divider>Peer [[ index + 1 ]]</a-divider> <td>
</td> <a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
</tr> </td>
<tr class="client-table-odd-row"> </tr>
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td> <tr v-if="app.ipLimitEnable && infoModal.clientSettings.limitIp > 0">
<td>[[ peer.privateKey ]]</td> <td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
</tr> <td>
<tr> <div style="max-height: 150px; overflow-y: auto; text-align: left;">
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td> <div v-if="infoModal.clientIpsArray && infoModal.clientIpsArray.length > 0">
<td>[[ peer.publicKey ]]</td> <a-tag v-for="(ipInfo, idx) in infoModal.clientIpsArray" :key="idx" color="blue"
</tr> style="margin: 2px 0; display: block; font-family: monospace; font-size: 11px;">
<tr class="client-table-odd-row"> [[ formatIpInfo(ipInfo) ]]
<td>{{ i18n "pages.xray.wireguard.psk" }}</td> </a-tag>
<td>[[ peer.psk ]]</td> </div>
</tr> <a-tag v-else>[[ infoModal.clientIps || 'No IP Record'
<tr> ]]</a-tag>
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td> </div>
<td>[[ peer.allowedIPs.join(",") ]]</td> <div style="margin-top: 5px;">
</tr> <a-icon type="sync" :spin="refreshing" @click="refreshIPs" :style="{ margin: '0 5px' }"></a-icon>
<tr class="client-table-odd-row"> <a-tooltip>
<td>Keep Alive</td> <template slot="title">
<td>[[ peer.keepAlive ]]</td> <span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
</tr> </template>
<tr> <a-icon type="delete" @click="clearClientIps"></a-icon>
<td colspan="2"> </a-tooltip>
<tr-info-row class="tr-info-row"> </div>
<tr-info-title class="tr-info-title"> </td>
<a-tag color="blue">Config</a-tag> </tr>
<a-tooltip title='{{ i18n "copy" }}'> </table>
<a-button :style="{ minWidth: '24px' }" size="small" <table :style="{ display: 'inline-table', marginBlock: '10px', width: '100%', textAlign: 'center' }">
icon="snippets" <tr>
@click="copy(infoModal.links[index])"></a-button> <th>{{ i18n "remained" }}</th>
</a-tooltip> <th>{{ i18n "pages.inbounds.totalFlow" }}</th>
<a-tooltip title='{{ i18n "download" }}'> <th>{{ i18n "pages.inbounds.expireDate" }}</th>
<a-button :style="{ minWidth: '24px' }" size="small" </tr>
icon="download" <tr>
@click="FileManager.downloadTextFile(infoModal.links[index], `peer-${index + 1}.conf`)"></a-button> <td>
</a-tooltip> <a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0"
</tr-info-title> :color="statsColor(infoModal.clientStats)"> [[ getRemStats()
<div ]] </a-tag>
v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" </td>
:style="{ borderRadius: '1rem', padding: '0.5rem' }" <td>
class="client-table-odd-row"> <a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[
</div> SizeFormatter.sizeFormat(infoModal.clientSettings.totalGB) ]]
</tr-info-row> </a-tag>
</td> <a-tag v-else color="purple" class="infinite-tag">
</tr> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
</table> <path
</template> d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"
fill="currentColor"></path>
</svg>
</a-tag>
</td>
<td>
<template v-if="infoModal.clientSettings.expiryTime > 0">
<a-tag
:color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
[[ IntlUtil.formatDate(infoModal.clientSettings.expiryTime)
]]
</a-tag>
</template>
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[
infoModal.clientSettings.expiryTime /
-86400000 ]] {{ i18n "pages.client.days" }}
</a-tag>
<a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path
d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"
fill="currentColor"></path>
</svg>
</a-tag>
</td>
</tr>
</table>
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription URL</a-divider>
<tr-info-row class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag color="purple">Subscription Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets" @click="copy(infoModal.subLink)"></a-button>
</a-tooltip>
</tr-info-title>
<a :href="[[ infoModal.subLink ]]" target="_blank">[[
infoModal.subLink ]]</a>
</tr-info-row>
<tr-info-row class="tr-info-row" v-if="app.subSettings.subJsonEnable">
<tr-info-title class="tr-info-title">
<a-tag color="purple">Json Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets" @click="copy(infoModal.subJsonLink)"></a-button>
</a-tooltip>
</tr-info-title>
<a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[
infoModal.subJsonLink ]]</a>
</tr-info-row>
</template>
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram ChatID</a-divider>
<tr-info-row class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets" @click="copy(infoModal.clientSettings.tgId)"></a-button>
</a-tooltip>
</tr-info-title>
</tr-info-row>
</template>
<template v-if="dbInbound.hasLink()">
<a-divider>URL</a-divider>
<tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark
]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button :style="{ minWidth: '24px' }" size="small" icon="snippets" @click="copy(link.link)"></a-button>
</a-tooltip>
</tr-info-title>
<code>[[ link.link ]]</code>
</tr-info-row>
</template>
</template>
<template v-else>
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
<a-divider>URL</a-divider>
<tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark
]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button :style="{ minWidth: '24px' }" size="small" icon="snippets" @click="copy(link.link)"></a-button>
</a-tooltip>
</tr-info-title>
<code>[[ link.link ]]</code>
</tr-info-row>
</template>
<table v-if="inbound.protocol == Protocols.TUNNEL" class="tr-info-table">
<tr>
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
<th>{{ i18n "pages.inbounds.network" }}</th>
<th>FollowRedirect</th>
</tr>
<tr>
<td>
<a-tag color="green">[[ inbound.settings.address ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.port ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.network ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.followRedirect
]]</a-tag>
</td>
</tr>
</table>
<table v-if="dbInbound.isMixed" class="tr-info-table">
<tr>
<th>{{ i18n "password" }} Auth</th>
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
<th>IP</th>
</tr>
<tr>
<td>
<a-tag color="green">[[ inbound.settings.auth ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.udp]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.ip ]]</a-tag>
</td>
</tr>
<template v-if="inbound.settings.auth == 'password'">
<tr>
<td></td>
<td>{{ i18n "username" }}</td>
<td>{{ i18n "password" }}</td>
</tr>
<tr v-for="account,index in inbound.settings.accounts">
<td>[[ index ]]</td>
<td>
<a-tag color="green">[[ account.user ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ account.pass ]]</a-tag>
</td>
</tr>
</template> </template>
</a-modal> </table>
<script> <table v-if="dbInbound.isHTTP" class="tr-info-table">
<tr>
<th></th>
<th>{{ i18n "username" }}</th>
<th>{{ i18n "password" }}</th>
</tr>
<tr v-for="account,index in inbound.settings.accounts">
<td>[[ index ]]</td>
<td>
<a-tag color="green">[[ account.user ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ account.pass ]]</a-tag>
</td>
</tr>
</table>
<table v-if="dbInbound.isWireguard" class="tr-info-table">
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ inbound.settings.secretKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ inbound.settings.pubKey ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>MTU</td>
<td>[[ inbound.settings.mtu ]]</td>
</tr>
<tr>
<td>No Kernel Tun</td>
<td>[[ inbound.settings.noKernelTun ]]</td>
</tr>
<template v-for="(peer, index) in inbound.settings.peers">
<tr>
<td colspan="2">
<a-divider>Peer [[ index + 1 ]]</a-divider>
</td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ peer.privateKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ peer.publicKey ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
<td>[[ peer.psk ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
<td>[[ peer.allowedIPs.join(",") ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Keep Alive</td>
<td>[[ peer.keepAlive ]]</td>
</tr>
<tr>
<td colspan="2">
<tr-info-row class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag color="blue">Config</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button :style="{ minWidth: '24px' }" size="small" icon="snippets"
@click="copy(infoModal.links[index])"></a-button>
</a-tooltip>
<a-tooltip title='{{ i18n "download" }}'>
<a-button :style="{ minWidth: '24px' }" size="small" icon="download"
@click="FileManager.downloadTextFile(infoModal.links[index], `peer-${index + 1}.conf`)"></a-button>
</a-tooltip>
</tr-info-title>
<div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)"
:style="{ borderRadius: '1rem', padding: '0.5rem' }" class="client-table-odd-row">
</div>
</tr-info-row>
</td>
</tr>
</table>
</template>
</template>
</a-modal>
<script>
function refreshIPs(email) { function refreshIPs(email) {
return HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`).then((msg) => { return HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`).then((msg) => {
if (!msg.success) { if (!msg.success) {
@ -654,9 +619,9 @@
if ( if (
[ [
Protocols.VMESS, Protocols.VMESS,
Protocols.VLESS, Protocols.VLESS,
Protocols.TROJAN, Protocols.TROJAN,
Protocols.SHADOWSOCKS Protocols.SHADOWSOCKS
].includes(this.inbound.protocol) ].includes(this.inbound.protocol)
) { ) {
@ -797,9 +762,9 @@
this.infoModal.clientIps = 'No IP Record'; this.infoModal.clientIps = 'No IP Record';
this.infoModal.clientIpsArray = []; this.infoModal.clientIpsArray = [];
}) })
.catch(() => {}); .catch(() => { });
}, },
}, },
}); });
</script> </script>
{{end}} {{end}}

View file

@ -5,10 +5,9 @@
{{template "form/inbound"}} {{template "form/inbound"}}
</a-modal> </a-modal>
<script> <script>
// Make inModal globally available to ensure it works with any base path // Make inModal globally available to ensure it works with any base path
const inModal = window.inModal = { const inModal = (window.inModal = {
title: '', title: "",
visible: false, visible: false,
confirmLoading: false, confirmLoading: false,
okText: '{{ i18n "sure" }}', okText: '{{ i18n "sure" }}',
@ -19,7 +18,14 @@
ok() { ok() {
ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound); ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
}, },
show({ title = '', okText = '{{ i18n "sure" }}', inbound = null, dbInbound = null, confirm = (inbound, dbInbound) => { }, isEdit = false }) { show({
title = "",
okText = '{{ i18n "sure" }}',
inbound = null,
dbInbound = null,
confirm = (inbound, dbInbound) => { },
isEdit = false,
}) {
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
if (inbound) { if (inbound) {
@ -30,7 +36,11 @@
// Always ensure testseed is initialized for VLESS protocol (even if vision flow is not set yet) // Always ensure testseed is initialized for VLESS protocol (even if vision flow is not set yet)
// This ensures Vue reactivity works properly // This ensures Vue reactivity works properly
if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) { if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) {
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed) || this.inbound.settings.testseed.length < 4) { if (
!this.inbound.settings.testseed ||
!Array.isArray(this.inbound.settings.testseed) ||
this.inbound.settings.testseed.length < 4
) {
// Create a new array to ensure Vue reactivity // Create a new array to ensure Vue reactivity
this.inbound.settings.testseed = [900, 500, 900, 256].slice(); this.inbound.settings.testseed = [900, 500, 900, 256].slice();
} }
@ -56,7 +66,10 @@
// Use inModal.inbound explicitly to ensure correct context // Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return; if (!inModal.inbound || !inModal.inbound.settings) return;
// Ensure testseed is initialized // Ensure testseed is initialized
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed)) { if (
!inModal.inbound.settings.testseed ||
!Array.isArray(inModal.inbound.settings.testseed)
) {
inModal.inbound.settings.testseed = [900, 500, 900, 256]; inModal.inbound.settings.testseed = [900, 500, 900, 256];
} }
// Ensure array has enough elements // Ensure array has enough elements
@ -70,26 +83,35 @@
// Use inModal.inbound explicitly to ensure correct context // Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return; if (!inModal.inbound || !inModal.inbound.settings) return;
// Ensure testseed is initialized // Ensure testseed is initialized
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed) || inModal.inbound.settings.testseed.length < 4) { if (
!inModal.inbound.settings.testseed ||
!Array.isArray(inModal.inbound.settings.testseed) ||
inModal.inbound.settings.testseed.length < 4
) {
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice(); inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
} }
// Create new array with random values // Create new array with random values
inModal.inbound.settings.testseed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)]; inModal.inbound.settings.testseed = [
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
];
}, },
resetTestseed() { resetTestseed() {
// Use inModal.inbound explicitly to ensure correct context // Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return; if (!inModal.inbound || !inModal.inbound.settings) return;
// Reset testseed to default values // Reset testseed to default values
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice(); inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
} },
}; });
// Store Vue instance globally to ensure methods are always accessible // Store Vue instance globally to ensure methods are always accessible
let inboundModalVueInstance = null; let inboundModalVueInstance = null;
inboundModalVueInstance = new Vue({ inboundModalVueInstance = new Vue({
delimiters: ['[[', ']]'], delimiters: ["[[", "]]"],
el: '#inbound-modal', el: "#inbound-modal",
data: { data: {
inModal: inModal, inModal: inModal,
delayedStart: false, delayedStart: false,
@ -103,13 +125,19 @@
return inModal.isEdit; return inModal.isEdit;
}, },
get client() { get client() {
return inModal.inbound && inModal.inbound.clients && inModal.inbound.clients.length > 0 ? inModal.inbound.clients[0] : null; return inModal.inbound &&
inModal.inbound.clients &&
inModal.inbound.clients.length > 0
? inModal.inbound.clients[0]
: null;
}, },
get datepicker() { get datepicker() {
return app.datepicker; return app.datepicker;
}, },
get delayedExpireDays() { get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0; return this.client && this.client.expiryTime < 0
? this.client.expiryTime / -86400000
: 0;
}, },
set delayedExpireDays(days) { set delayedExpireDays(days) {
this.client.expiryTime = -86400000 * days; this.client.expiryTime = -86400000 * days;
@ -119,72 +147,103 @@
}, },
set externalProxy(value) { set externalProxy(value) {
if (value) { if (value) {
inModal.inbound.stream.externalProxy = [{ inModal.inbound.stream.externalProxy = [
forceTls: "same", {
dest: window.location.hostname, forceTls: "same",
port: inModal.inbound.port, dest: window.location.hostname,
remark: "" port: inModal.inbound.port,
}]; remark: "",
},
];
} else { } else {
inModal.inbound.stream.externalProxy = []; inModal.inbound.stream.externalProxy = [];
} }
} },
}, },
watch: { watch: {
'inModal.inbound.stream.security'(newVal, oldVal) { "inModal.inbound.stream.security"(newVal, oldVal) {
// Clear flow when security changes from reality/tls to none // Clear flow when security changes from reality/tls to none
if (inModal.inbound.protocol == Protocols.VLESS && !inModal.inbound.canEnableTlsFlow()) { if (
inModal.inbound.settings.vlesses.forEach(client => { inModal.inbound.protocol == Protocols.VLESS &&
!inModal.inbound.canEnableTlsFlow()
) {
inModal.inbound.settings.vlesses.forEach((client) => {
client.flow = ""; client.flow = "";
}); });
} }
}, },
// Ensure testseed is always initialized when vision flow is enabled // Ensure testseed is always initialized when vision flow is enabled
'inModal.inbound.settings.vlesses': { "inModal.inbound.settings.vlesses": {
handler() { handler() {
if (inModal.inbound.protocol === Protocols.VLESS && inModal.inbound.settings && inModal.inbound.settings.vlesses) { if (
const hasVisionFlow = inModal.inbound.settings.vlesses.some(c => c.flow === 'xtls-rprx-vision' || c.flow === 'xtls-rprx-vision-udp443'); inModal.inbound.protocol === Protocols.VLESS &&
if (hasVisionFlow && (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed) || inModal.inbound.settings.testseed.length < 4)) { inModal.inbound.settings &&
inModal.inbound.settings.vlesses
) {
const hasVisionFlow = inModal.inbound.settings.vlesses.some(
(c) =>
c.flow === "xtls-rprx-vision" ||
c.flow === "xtls-rprx-vision-udp443",
);
if (
hasVisionFlow &&
(!inModal.inbound.settings.testseed ||
!Array.isArray(inModal.inbound.settings.testseed) ||
inModal.inbound.settings.testseed.length < 4)
) {
inModal.inbound.settings.testseed = [900, 500, 900, 256]; inModal.inbound.settings.testseed = [900, 500, 900, 256];
} }
} }
}, },
deep: true deep: true,
} },
}, },
methods: { methods: {
streamNetworkChange() { streamNetworkChange() {
if (!inModal.inbound.canEnableTls()) { if (!inModal.inbound.canEnableTls()) {
this.inModal.inbound.stream.security = 'none'; this.inModal.inbound.stream.security = "none";
} }
if (!inModal.inbound.canEnableReality()) { if (!inModal.inbound.canEnableReality()) {
this.inModal.inbound.reality = false; this.inModal.inbound.reality = false;
} }
if (this.inModal.inbound.protocol == Protocols.VLESS && !inModal.inbound.canEnableTlsFlow()) { if (
this.inModal.inbound.settings.vlesses.forEach(client => { this.inModal.inbound.protocol == Protocols.VLESS &&
!inModal.inbound.canEnableTlsFlow()
) {
this.inModal.inbound.settings.vlesses.forEach((client) => {
client.flow = ""; client.flow = "";
}); });
} }
if (inModal.inbound.stream.network != "kcp") {
inModal.inbound.stream.finalmask.udp = [];
}
}, },
SSMethodChange() { SSMethodChange() {
this.inModal.inbound.settings.password = RandomUtil.randomShadowsocksPassword(this.inModal.inbound.settings.method) this.inModal.inbound.settings.password =
RandomUtil.randomShadowsocksPassword(
this.inModal.inbound.settings.method,
);
if (this.inModal.inbound.isSSMultiUser) { if (this.inModal.inbound.isSSMultiUser) {
if (this.inModal.inbound.settings.shadowsockses.length == 0) { if (this.inModal.inbound.settings.shadowsockses.length == 0) {
this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()]; this.inModal.inbound.settings.shadowsockses = [
new Inbound.ShadowsocksSettings.Shadowsocks(),
];
} }
if (!this.inModal.inbound.isSS2022) { if (!this.inModal.inbound.isSS2022) {
this.inModal.inbound.settings.shadowsockses.forEach(client => { this.inModal.inbound.settings.shadowsockses.forEach((client) => {
client.method = this.inModal.inbound.settings.method; client.method = this.inModal.inbound.settings.method;
}) });
} else { } else {
this.inModal.inbound.settings.shadowsockses.forEach(client => { this.inModal.inbound.settings.shadowsockses.forEach((client) => {
client.method = ""; client.method = "";
}) });
} }
this.inModal.inbound.settings.shadowsockses.forEach(client => { this.inModal.inbound.settings.shadowsockses.forEach((client) => {
client.password = RandomUtil.randomShadowsocksPassword(this.inModal.inbound.settings.method) client.password = RandomUtil.randomShadowsocksPassword(
}) this.inModal.inbound.settings.method,
);
});
} else { } else {
if (this.inModal.inbound.settings.shadowsockses.length > 0) { if (this.inModal.inbound.settings.shadowsockses.length > 0) {
this.inModal.inbound.settings.shadowsockses = []; this.inModal.inbound.settings.shadowsockses = [];
@ -197,7 +256,7 @@
}, },
async getNewX25519Cert() { async getNewX25519Cert() {
inModal.loading(true); inModal.loading(true);
const msg = await HttpUtil.get('/panel/api/server/getNewX25519Cert'); const msg = await HttpUtil.get("/panel/api/server/getNewX25519Cert");
inModal.loading(false); inModal.loading(false);
if (!msg.success) { if (!msg.success) {
return; return;
@ -206,12 +265,12 @@
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey; inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
}, },
clearX25519Cert() { clearX25519Cert() {
this.inbound.stream.reality.privateKey = ''; this.inbound.stream.reality.privateKey = "";
this.inbound.stream.reality.settings.publicKey = ''; this.inbound.stream.reality.settings.publicKey = "";
}, },
async getNewmldsa65() { async getNewmldsa65() {
inModal.loading(true); inModal.loading(true);
const msg = await HttpUtil.get('/panel/api/server/getNewmldsa65'); const msg = await HttpUtil.get("/panel/api/server/getNewmldsa65");
inModal.loading(false); inModal.loading(false);
if (!msg.success) { if (!msg.success) {
return; return;
@ -220,11 +279,11 @@
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify; inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
}, },
clearMldsa65() { clearMldsa65() {
this.inbound.stream.reality.mldsa65Seed = ''; this.inbound.stream.reality.mldsa65Seed = "";
this.inbound.stream.reality.settings.mldsa65Verify = ''; this.inbound.stream.reality.settings.mldsa65Verify = "";
}, },
randomizeRealityTarget() { randomizeRealityTarget() {
if (typeof getRandomRealityTarget !== 'undefined') { if (typeof getRandomRealityTarget !== "undefined") {
const randomTarget = getRandomRealityTarget(); const randomTarget = getRandomRealityTarget();
this.inbound.stream.reality.target = randomTarget.target; this.inbound.stream.reality.target = randomTarget.target;
this.inbound.stream.reality.serverNames = randomTarget.sni; this.inbound.stream.reality.serverNames = randomTarget.sni;
@ -232,21 +291,24 @@
}, },
async getNewEchCert() { async getNewEchCert() {
inModal.loading(true); inModal.loading(true);
const msg = await HttpUtil.post('/panel/api/server/getNewEchCert', { sni: inModal.inbound.stream.tls.sni }); const msg = await HttpUtil.post("/panel/api/server/getNewEchCert", {
sni: inModal.inbound.stream.tls.sni,
});
inModal.loading(false); inModal.loading(false);
if (!msg.success) { if (!msg.success) {
return; return;
} }
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys; inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList; inModal.inbound.stream.tls.settings.echConfigList =
msg.obj.echConfigList;
}, },
clearEchCert() { clearEchCert() {
this.inbound.stream.tls.echServerKeys = ''; this.inbound.stream.tls.echServerKeys = "";
this.inbound.stream.tls.settings.echConfigList = ''; this.inbound.stream.tls.settings.echConfigList = "";
}, },
async getNewVlessEnc() { async getNewVlessEnc() {
inModal.loading(true); inModal.loading(true);
const msg = await HttpUtil.get('/panel/api/server/getNewVlessEnc'); const msg = await HttpUtil.get("/panel/api/server/getNewVlessEnc");
inModal.loading(false); inModal.loading(false);
if (!msg.success) { if (!msg.success) {
@ -255,7 +317,7 @@
const auths = msg.obj.auths || []; const auths = msg.obj.auths || [];
const selected = inModal.inbound.settings.selectedAuth; const selected = inModal.inbound.settings.selectedAuth;
const block = auths.find(a => a.label === selected); const block = auths.find((a) => a.label === selected);
if (!block) { if (!block) {
console.error("No auth block for", selected); console.error("No auth block for", selected);
@ -266,15 +328,18 @@
inModal.inbound.settings.encryption = block.encryption; inModal.inbound.settings.encryption = block.encryption;
}, },
clearVlessEnc() { clearVlessEnc() {
this.inbound.settings.decryption = 'none'; this.inbound.settings.decryption = "none";
this.inbound.settings.encryption = 'none'; this.inbound.settings.encryption = "none";
this.inbound.settings.selectedAuth = undefined; this.inbound.settings.selectedAuth = undefined;
}, },
// Vision Seed methods - must be in Vue methods for proper binding // Vision Seed methods - must be in Vue methods for proper binding
updateTestseed(index, value) { updateTestseed(index, value) {
// Ensure testseed is initialized // Ensure testseed is initialized
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed)) { if (
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]); !this.inbound.settings.testseed ||
!Array.isArray(this.inbound.settings.testseed)
) {
this.$set(this.inbound.settings, "testseed", [900, 500, 900, 256]);
} }
// Ensure array has enough elements // Ensure array has enough elements
while (this.inbound.settings.testseed.length <= index) { while (this.inbound.settings.testseed.length <= index) {
@ -285,15 +350,19 @@
}, },
setRandomTestseed() { setRandomTestseed() {
// Create new array with random values and use Vue.set for reactivity // Create new array with random values and use Vue.set for reactivity
const newSeed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)]; const newSeed = [
this.$set(this.inbound.settings, 'testseed', newSeed); Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
];
this.$set(this.inbound.settings, "testseed", newSeed);
}, },
resetTestseed() { resetTestseed() {
// Reset testseed to default values using Vue.set for reactivity // Reset testseed to default values using Vue.set for reactivity
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]); this.$set(this.inbound.settings, "testseed", [900, 500, 900, 256]);
} },
}, },
}); });
</script> </script>
{{end}} {{end}}