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 'xdns':
return { domain: settings.domain || '' };
case 'xicmp':
return { ip: settings.ip || '', id: settings.id ?? 0 };
case 'mkcp-original':
case 'header-dtls':
case 'header-srtp':
@ -586,6 +588,12 @@ class UdpMask extends CommonClass {
case 'header-wechat':
case 'header-wireguard':
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:
return settings;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,9 @@
{{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-switch v-model="inbound.settings.udp"></a-switch>
</a-form-item>
@ -7,7 +11,10 @@
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
<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>
<template v-if="inbound.settings.auth === 'password'">
<table :style="{ width: '100%', textAlign: 'center', margin: '1rem 0' }">
@ -15,17 +22,39 @@
<td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</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>
</tr>
</table>
<a-input-group compact 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-group
compact
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 :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">
<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>
</a-input>
</a-input-group>

View file

@ -11,40 +11,43 @@
<th>{{ i18n "pages.inbounds.email" }}</th>
<th>Password</th>
</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.password ]]</td>
</tr>
</table>
</a-collapse-panel>
</a-collapse>
<template v-if="inbound.isTcp">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Fallbacks">
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
</a-form-item>
</a-form>
<template v-if=" inbound.isTcp">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Fallbacks">
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
</a-form-item>
</a-form>
<!-- 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-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>
</a-divider>
<a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input>
</a-form-item>
<a-form-item label='ALPN'>
<a-input v-model="fallback.alpn"></a-input>
</a-form-item>
<a-form-item label='Path'>
<a-input v-model="fallback.path"></a-input>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model="fallback.dest"></a-input>
</a-form-item>
<a-form-item label='xVer'>
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:5px 0;"></a-divider>
</template>
{{end}}
<!-- 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-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>
</a-divider>
<a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input>
</a-form-item>
<a-form-item label='ALPN'>
<a-input v-model="fallback.alpn"></a-input>
</a-form-item>
<a-form-item label='Path'>
<a-input v-model="fallback.path"></a-input>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model="fallback.dest"></a-input>
</a-form-item>
<a-form-item label='xVer'>
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:5px 0;"></a-divider>
</template>
{{end}}

View file

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

View file

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

View file

@ -1,5 +1,5 @@
{{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" }}'>
{{template "form/client"}}
</a-collapse-panel>
@ -12,7 +12,8 @@
<th>ID</th>
<th>{{ i18n "security" }}</th>
</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.id ]]</td>
<td>[[ client.security ]]</td>
@ -20,4 +21,4 @@
</table>
</a-collapse-panel>
</a-collapse>
{{end}}
{{end}}

View file

@ -7,7 +7,8 @@
<span>{{ i18n "reset" }}</span>
</template>
{{ 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>
</template>
<a-input v-model.trim="inbound.settings.secretKey"></a-input>
@ -24,8 +25,11 @@
<a-form-item label="Peers">
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addPeer()"></a-button>
</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-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-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }"
: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-form-item>
<template slot="label">
@ -34,7 +38,8 @@
<span>{{ i18n "reset" }}</span>
</template>
{{ 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>
</template>
<a-input v-model.trim="peer.privateKey"></a-input>
@ -64,7 +69,8 @@
</template>
<template v-for="(aip, index) in peer.allowedIPs" :style="{ marginBottom: '10px' }">
<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>
</template>
</a-form-item>

View file

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

View file

@ -1,84 +1,167 @@
{{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} }" :wrapper-col="{ md: {span:14} }"
v-if="inbound.protocol == Protocols.HYSTERIA || inbound.stream.network == 'kcp'">
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
<a-form-item label="UDP Masks">
<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>
<template
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"
<template 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"
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
<a-icon type="delete"
@click="() => inbound.stream.delUdpMask(index)"
<a-divider :style="{ margin: '0' }">
UDP Mask [[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.stream.delUdpMask(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Type'>
<a-form-item label='{{ i18n "pages.xray.outbound.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; } }"
:dropdown-class-name="themeSwitcher.currentTheme">
<!-- mKCP-specific masks -->
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="mkcp-aes128gcm">
mKCP AES-128-GCM</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-dns">
Header DNS</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-dtls">
Header DTLS 1.2</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-srtp">
Header SRTP</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-utp">
Header uTP</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-wechat">
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>
<template v-if="inbound.protocol === Protocols.HYSTERIA">
<a-select-option value="salamander">Salamander (Hysteria2)</a-select-option>
</template>
<template v-else>
<a-select-option value="mkcp-aes128gcm">mKCP AES-128-GCM</a-select-option>
<a-select-option value="header-dns">Header DNS</a-select-option>
<a-select-option value="header-dtls">Header DTLS 1.2</a-select-option>
<a-select-option value="header-srtp">Header SRTP</a-select-option>
<a-select-option value="header-utp">Header uTP</a-select-option>
<a-select-option value="header-wechat">Header WeChat Video</a-select-option>
<a-select-option value="header-wireguard">Header WireGuard</a-select-option>
<a-select-option value="mkcp-original">mKCP Original</a-select-option>
<a-select-option value="xdns">xDNS</a-select-option>
<a-select-option value="xicmp">xICMP</a-select-option>
<a-select-option value="header-custom">Header Custom</a-select-option>
<a-select-option value="noise">Noise</a-select-option>
<a-select-option value="sudoku">Sudoku</a-select-option>
</template>
</a-select>
</a-form-item>
<!-- Settings for password-based masks -->
<a-form-item label='Password'
v-if="['mkcp-aes128gcm'].includes(mask.type)">
<a-input v-model.trim="mask.settings.password"
placeholder="Obfuscation password"></a-input>
<a-form-item label="Password" v-if="['mkcp-aes128gcm', 'salamander', 'sudoku'].includes(mask.type)">
<a-input v-model.trim="mask.settings.password" placeholder="Obfuscation password"></a-input>
</a-form-item>
<!-- Settings for domain-based masks -->
<a-form-item label='Domain'
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 label="Domain" 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>
<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>
</template>
</a-form>
{{end}}
{{end}}

View file

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

View file

@ -14,13 +14,16 @@
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<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>
</a-input>
<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.httpupgrade.removeHeader(index)"></a-button>
<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.httpupgrade.removeHeader(index)"></a-button>
</a-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}
{{end}}

View file

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

View file

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

View file

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

View file

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

View file

@ -5,11 +5,13 @@
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
</a-form-item>
<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>
<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 -->
<a-divider :style="{ margin: '0' }">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
@ -24,7 +26,8 @@
</template>
<template v-for="(path, index) in inbound.stream.tcp.request.path">
<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>
</template>
</a-form-item>
@ -33,11 +36,14 @@
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<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>
</a-input>
<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.tcp.request.removeHeader(index)"></a-button>
<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.tcp.request.removeHeader(index)"></a-button>
</a-input>
</a-input-group>
</a-form-item>
@ -54,14 +60,17 @@
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
</a-form-item>
<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 :wrapper-col="{span:24}">
<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>
</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">
<a-button icon="minus" size="small" @click="inbound.stream.tcp.response.removeHeader(index)"></a-button>
</template>
@ -69,4 +78,4 @@
</a-input-group>
</a-form-item>
</a-form>
{{end}}
{{end}}

View file

@ -17,13 +17,15 @@
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<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>
</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-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}
{{end}}

View file

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

View file

@ -1,8 +1,6 @@
{{define "modals/inboundInfoModal"}}
<a-modal id="inbound-info-modal" v-model="infoModal.visible"
title='{{ i18n "pages.inbounds.details"}}' :closable="true"
:mask-closable="true" :footer="null" width="600px"
:class="themeSwitcher.currentTheme">
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' :closable="true"
:mask-closable="true" :footer="null" width="600px" :class="themeSwitcher.currentTheme">
<a-row>
<a-col :xs="24" :md="12">
<table>
@ -29,8 +27,7 @@
</table>
</a-col>
<a-col :xs="24" :md="12">
<template
v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<table>
<tr>
<td>{{ i18n "transmission" }}</td>
@ -38,8 +35,7 @@
<a-tag color="green">[[ inbound.network ]]</a-tag>
</td>
</tr>
<template
v-if="inbound.isTcp || inbound.isWs || inbound.isHttpupgrade || inbound.isXHTTP">
<template v-if="inbound.isTcp || inbound.isWs || inbound.isHttpupgrade || inbound.isXHTTP">
<tr>
<td>{{ i18n "host" }}</td>
<td v-if="inbound.host">
@ -51,13 +47,13 @@
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td v-if="inbound.path">
<a-tooltip :title="[[ inbound.path ]]">
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
</a-tooltip>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td v-if="inbound.path">
<a-tooltip :title="[[ inbound.path ]]">
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
</a-tooltip>
<td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
@ -79,483 +75,452 @@
<a-tag class="info-large-tag">[[ inbound.serviceName
]]</a-tag>
</a-tooltip>
<tr>
<td>grpc multiMode</td>
<td>
<a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag>
</td>
</tr>
</template>
</table>
<tr>
<td>grpc multiMode</td>
<td>
<a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag>
</td>
</tr>
</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>
<template v-if="infoModal.clientSettings">
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<table :style="{ marginBottom: '10px' }">
<tr>
<td>{{ i18n "pages.inbounds.email" }}</td>
<td v-if="infoModal.clientSettings.email">
<a-tag color="green">[[ infoModal.clientSettings.email
]]</a-tag>
</td>
<td v-else>
<a-tag color="red">{{ i18n "none" }}</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientSettings.id">
<td>ID</td>
<td>
<a-tag>[[ infoModal.clientSettings.id ]]</a-tag>
</td>
</tr>
<tr v-if="dbInbound.isVMess">
<td>{{ i18n "security" }}</td>
<td>
<a-tag>[[ infoModal.clientSettings.security ]]</a-tag>
</td>
</tr>
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
<td>Flow</td>
<td v-if="infoModal.clientSettings.flow">
<a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
</td>
<td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientSettings.password">
<td>{{ i18n "password" }}</td>
<td>
<a-tooltip :title="[[ infoModal.clientSettings.password ]]">
<a-tag class="info-large-tag">[[
infoModal.clientSettings.password ]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr>
<td>{{ i18n "status" }}</td>
<td>
<a-tag v-if="isDepleted" color="red">{{ i18n "depleted"
}}</a-tag>
<a-tag v-else-if="isEnable" color="green">{{ i18n "enabled"
}}</a-tag>
<a-tag v-else>{{ i18n "disabled" }}</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientStats">
<td>{{ i18n "usage" }}</td>
<td>
<a-tag color="green">[[
SizeFormatter.sizeFormat(infoModal.clientStats.up +
infoModal.clientStats.down) ]]</a-tag>
<a-tag>↑ [[ SizeFormatter.sizeFormat(infoModal.clientStats.up)
]] / [[ SizeFormatter.sizeFormat(infoModal.clientStats.down)
]] ↓</a-tag>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.createdAt" }}</td>
<td>
<template
v-if="infoModal.clientSettings && infoModal.clientSettings.created_at">
<a-tag>[[
IntlUtil.formatDate(infoModal.clientSettings.created_at)
]]</a-tag>
</template>
<template v-else>
<a-tag>-</a-tag>
</template>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.updatedAt" }}</td>
<td>
<template
v-if="infoModal.clientSettings && infoModal.clientSettings.updated_at">
<a-tag>[[
IntlUtil.formatDate(infoModal.clientSettings.updated_at)
]]</a-tag>
</template>
<template v-else>
<a-tag>-</a-tag>
</template>
</td>
</tr>
<tr>
<td>{{ i18n "lastOnline" }}</td>
<td>
<a-tag>[[ app.formatLastOnline(infoModal.clientSettings &&
infoModal.clientSettings.email ?
infoModal.clientSettings.email : '') ]]</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientSettings.comment">
<td>{{ i18n "comment" }}</td>
<td>
<a-tooltip :title="[[ infoModal.clientSettings.comment ]]">
<a-tag class="info-large-tag">[[
infoModal.clientSettings.comment ]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr v-if="app.ipLimitEnable">
<td>{{ i18n "pages.inbounds.IPLimit" }}</td>
<td>
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
</td>
</tr>
<tr
v-if="app.ipLimitEnable && infoModal.clientSettings.limitIp > 0">
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
<td>
<div
style="max-height: 150px; overflow-y: auto; text-align: left;">
<div
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>
</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>
<template v-if="infoModal.clientSettings">
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<table :style="{ marginBottom: '10px' }">
<tr>
<td>{{ i18n "pages.inbounds.email" }}</td>
<td v-if="infoModal.clientSettings.email">
<a-tag color="green">[[ infoModal.clientSettings.email
]]</a-tag>
</td>
<td v-else>
<a-tag color="red">{{ i18n "none" }}</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientSettings.id">
<td>ID</td>
<td>
<a-tag>[[ infoModal.clientSettings.id ]]</a-tag>
</td>
</tr>
<tr v-if="dbInbound.isVMess">
<td>{{ i18n "security" }}</td>
<td>
<a-tag>[[ infoModal.clientSettings.security ]]</a-tag>
</td>
</tr>
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
<td>Flow</td>
<td v-if="infoModal.clientSettings.flow">
<a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
</td>
<td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientSettings.password">
<td>{{ i18n "password" }}</td>
<td>
<a-tooltip :title="[[ infoModal.clientSettings.password ]]">
<a-tag class="info-large-tag">[[
infoModal.clientSettings.password ]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr>
<td>{{ i18n "status" }}</td>
<td>
<a-tag v-if="isDepleted" color="red">{{ i18n "depleted"
}}</a-tag>
<a-tag v-else-if="isEnable" color="green">{{ i18n "enabled"
}}</a-tag>
<a-tag v-else>{{ i18n "disabled" }}</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientStats">
<td>{{ i18n "usage" }}</td>
<td>
<a-tag color="green">[[
SizeFormatter.sizeFormat(infoModal.clientStats.up +
infoModal.clientStats.down) ]]</a-tag>
<a-tag>↑ [[ SizeFormatter.sizeFormat(infoModal.clientStats.up)
]] / [[ SizeFormatter.sizeFormat(infoModal.clientStats.down)
]] ↓</a-tag>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.createdAt" }}</td>
<td>
<template v-if="infoModal.clientSettings && infoModal.clientSettings.created_at">
<a-tag>[[
IntlUtil.formatDate(infoModal.clientSettings.created_at)
]]</a-tag>
</template>
</table>
<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 v-else>
<a-tag>-</a-tag>
</template>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.updatedAt" }}</td>
<td>
<template v-if="infoModal.clientSettings && infoModal.clientSettings.updated_at">
<a-tag>[[
IntlUtil.formatDate(infoModal.clientSettings.updated_at)
]]</a-tag>
</template>
<template v-else>
<a-tag>-</a-tag>
</template>
</td>
</tr>
<tr>
<td>{{ i18n "lastOnline" }}</td>
<td>
<a-tag>[[ app.formatLastOnline(infoModal.clientSettings &&
infoModal.clientSettings.email ?
infoModal.clientSettings.email : '') ]]</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientSettings.comment">
<td>{{ i18n "comment" }}</td>
<td>
<a-tooltip :title="[[ infoModal.clientSettings.comment ]]">
<a-tag class="info-large-tag">[[
infoModal.clientSettings.comment ]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr v-if="app.ipLimitEnable">
<td>{{ i18n "pages.inbounds.IPLimit" }}</td>
<td>
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
</td>
</tr>
<tr v-if="app.ipLimitEnable && infoModal.clientSettings.limitIp > 0">
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
<td>
<div style="max-height: 150px; overflow-y: auto; text-align: left;">
<div 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>
</a-modal>
<script>
</table>
<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) {
return HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`).then((msg) => {
if (!msg.success) {
@ -654,9 +619,9 @@
if (
[
Protocols.VMESS,
Protocols.VMESS,
Protocols.VLESS,
Protocols.TROJAN,
Protocols.TROJAN,
Protocols.SHADOWSOCKS
].includes(this.inbound.protocol)
) {
@ -797,9 +762,9 @@
this.infoModal.clientIps = 'No IP Record';
this.infoModal.clientIpsArray = [];
})
.catch(() => {});
.catch(() => { });
},
},
});
</script>
{{end}}
{{end}}

View file

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