소스 검색

finalmask

Co-Authored-By: Alireza Ahmadi <[email protected]>
MHSanaei 1 일 전
부모
커밋
04b4fb4384

+ 8 - 0
web/assets/js/model/outbound.js

@@ -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;
         }

+ 26 - 53
web/html/form/client.html

@@ -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">

+ 10 - 20
web/html/form/inbound.html

@@ -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>

+ 241 - 259
web/html/form/outbound.html

@@ -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>

+ 70 - 44
web/html/form/protocol/shadowsocks.html

@@ -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}}

+ 37 - 8
web/html/form/protocol/socks.html

@@ -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>

+ 33 - 30
web/html/form/protocol/trojan.html

@@ -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}}

+ 47 - 38
web/html/form/protocol/tun.html

@@ -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}}

+ 1 - 1
web/html/form/protocol/vless.html

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

+ 4 - 3
web/html/form/protocol/vmess.html

@@ -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}}

+ 11 - 5
web/html/form/protocol/wireguard.html

@@ -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>

+ 2 - 4
web/html/form/reality_settings.html

@@ -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>

+ 150 - 67
web/html/form/stream/stream_finalmask.html

@@ -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}}

+ 1 - 1
web/html/form/stream/stream_grpc.html

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

+ 7 - 4
web/html/form/stream/stream_httpupgrade.html

@@ -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}}

+ 13 - 26
web/html/form/stream/stream_hysteria.html

@@ -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>

+ 8 - 15
web/html/form/stream/stream_kcp.html

@@ -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}}

+ 25 - 28
web/html/form/stream/stream_settings.html

@@ -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}}

+ 9 - 6
web/html/form/stream/stream_sockopt.html

@@ -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}}

+ 19 - 10
web/html/form/stream/stream_tcp.html

@@ -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}}

+ 5 - 3
web/html/form/stream/stream_ws.html

@@ -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}}

+ 23 - 44
web/html/form/stream/stream_xhttp.html

@@ -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>

+ 448 - 483
web/html/modals/inbound_info_modal.html

@@ -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>
+        </table>
+      </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>
-          <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%' }">
+        </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>{{ i18n "encryption" }}</td>
+            <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">[[ inbound.settings.method ]]</a-tag>
+              <a-tag color="green">[[ account.user ]]</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>
+              <a-tag color="green">[[ account.pass ]]</a-tag>
             </td>
           </tr>
+        </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>{{ i18n "pages.inbounds.network" }}</td>
-            <td>
-              <a-tag color="green">[[ inbound.settings.network ]]</a-tag>
+            <td colspan="2">
+              <a-divider>Peer [[ index + 1 ]]</a-divider>
             </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>
+          <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>
-              </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>
-          </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>
+              </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}}

+ 134 - 65
web/html/modals/inbound_modal.html

@@ -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}}