Kaynağa Gözat

reset button for auth password

MHSanaei 1 gün önce
ebeveyn
işleme
6d0e7ec495

+ 245 - 180
web/html/form/client.html

@@ -1,191 +1,256 @@
 {{define "form/client"}}
-<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>
-    <a-form-item>
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">
-                    <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
-                </template>
-                {{ i18n "pages.inbounds.email" }}
-                <a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
-            </a-tooltip>
+<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>
+  <a-form-item>
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
         </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">
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">
-                    <span>{{ i18n "reset" }}</span>
-                </template>
-                {{ i18n "password" }}
-                <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)"
-                    type="sync"> </a-icon>
-            </a-tooltip>
+        {{ i18n "pages.inbounds.email" }}
+        <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"
+  >
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          <span>{{ i18n "reset" }}</span>
         </template>
-        <a-input v-model.trim="client.password"></a-input>
-    </a-form-item>
-    <a-form-item v-if="inbound.protocol === Protocols.HYSTERIA">
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">
-                    <span>{{ i18n "reset" }}</span>
-                </template>
-                Auth Password
-                <a-icon @click="client.auth = RandomUtil.randomSeq(10)" type="sync"></a-icon>
-            </a-tooltip>
+        {{ i18n "password" }}
+        <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)"
+          type="sync"
+        >
+        </a-icon>
+      </a-tooltip>
+    </template>
+    <a-input v-model.trim="client.password"></a-input>
+  </a-form-item>
+  <a-form-item v-if="inbound.protocol === Protocols.HYSTERIA">
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          <span>{{ i18n "reset" }}</span>
         </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">
-        <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>
-            </a-tooltip>
+        Auth Password
+        <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"
+  >
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          <span>{{ i18n "reset" }}</span>
         </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-select-option v-for="key in USERS_SECURITY" :value="key">[[ key
-                ]]</a-select-option>
-        </a-select>
-    </a-form-item>
-    <a-form-item v-if="client.email && app.subSettings?.enable">
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">
-                    <span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
-                </template>
-                Subscription
-                <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
-            </a-tooltip>
+        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-select-option v-for="key in USERS_SECURITY" :value="key"
+        >[[ key ]]</a-select-option
+      >
+    </a-select>
+  </a-form-item>
+  <a-form-item v-if="client.email && app.subSettings?.enable">
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          <span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
         </template>
-        <a-input v-model.trim="client.subId"></a-input>
-    </a-form-item>
-    <a-form-item v-if="client.email && app.tgBotEnable">
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">
-                    <span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
-                </template>
-                Telegram ChatID
-                <a-icon type="question-circle"></a-icon>
-            </a-tooltip>
+        Subscription
+        <a-icon
+          @click="client.subId = RandomUtil.randomLowerAndNum(16)"
+          type="sync"
+        ></a-icon>
+      </a-tooltip>
+    </template>
+    <a-input v-model.trim="client.subId"></a-input>
+  </a-form-item>
+  <a-form-item v-if="client.email && app.tgBotEnable">
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          <span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
         </template>
-        <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>
-    </a-form-item>
-    <a-form-item v-if="app.ipLimitEnable">
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">
-                    <span>{{ i18n "pages.inbounds.IPLimitDesc"}}</span>
-                </template>
-                <span>{{ i18n "pages.inbounds.IPLimit"}} </span>
-                <a-icon type="question-circle"></a-icon>
-            </a-tooltip>
+        Telegram ChatID
+        <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-form-item>
+  <a-form-item v-if="client.email" label='{{ i18n "comment" }}'>
+    <a-input v-model.trim="client.comment"></a-input>
+  </a-form-item>
+  <a-form-item v-if="app.ipLimitEnable">
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          <span>{{ i18n "pages.inbounds.IPLimitDesc"}}</span>
         </template>
-        <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">
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">
-                    <span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
-                </template>
-                <span>{{ i18n "pages.inbounds.IPLimitlog" }} </span>
-                <a-icon type="question-circle"></a-icon>
-            </a-tooltip>
+        <span>{{ i18n "pages.inbounds.IPLimit"}} </span>
+        <a-icon type="question-circle"></a-icon>
+      </a-tooltip>
+    </template>
+    <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"
+  >
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          <span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
         </template>
-        <a-tooltip>
-            <template slot="title">
-                <span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
-            </template>
-            <span :style="{ color: '#FF4D4F' }">
-                <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"
-                :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-option value selected>{{ i18n "none" }}</a-select-option>
-            <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key
-                ]]</a-select-option>
-        </a-select>
-    </a-form-item>
-    <a-form-item>
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">
-                    0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
-                </template>
-                {{ i18n "pages.inbounds.totalFlow" }}
-                <a-icon type="question-circle"></a-icon>
-            </a-tooltip>
+        <span>{{ i18n "pages.inbounds.IPLimitlog" }} </span>
+        <a-icon type="question-circle"></a-icon>
+      </a-tooltip>
+    </template>
+    <a-tooltip>
+      <template slot="title">
+        <span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
+      </template>
+      <span :style="{ color: '#FF4D4F' }">
+        <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"
+        :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-option value selected>{{ i18n "none" }}</a-select-option>
+      <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key"
+        >[[ key ]]</a-select-option
+      >
+    </a-select>
+  </a-form-item>
+  <a-form-item>
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">
+          0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
         </template>
-        <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)">
-            [[ SizeFormatter.sizeFormat(clientStats.up) ]] /
-            [[ SizeFormatter.sizeFormat(clientStats.down) ]]
-            ([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]])
-        </a-tag>
-        <a-tooltip>
-            <template slot="title">{{ i18n "pages.inbounds.resetTraffic"
-                }}</template>
-            <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-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>
-    <a-form-item v-else>
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">{{ i18n
-                    "pages.inbounds.leaveBlankToNeverExpire" }}</template>
-                {{ i18n "pages.inbounds.expireDate" }}
-                <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-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
-    </a-form-item>
-    <a-form-item v-if="client.expiryTime != 0">
-        <template slot="label">
-            <a-tooltip>
-                <template slot="title">{{ i18n "pages.client.renewDesc"
-                    }}</template>
-                {{ i18n "pages.client.renew" }}
-                <a-icon type="question-circle"></a-icon>
-            </a-tooltip>
-        </template>
-        <a-input-number v-model.number="client.reset" :min="0"></a-input-number>
-    </a-form-item>
+        {{ i18n "pages.inbounds.totalFlow" }}
+        <a-icon type="question-circle"></a-icon>
+      </a-tooltip>
+    </template>
+    <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)">
+      [[ SizeFormatter.sizeFormat(clientStats.up) ]] / [[
+      SizeFormatter.sizeFormat(clientStats.down) ]] ([[
+      SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]])
+    </a-tag>
+    <a-tooltip>
+      <template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
+      <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-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>
+  <a-form-item v-else>
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title"
+          >{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</template
+        >
+        {{ i18n "pages.inbounds.expireDate" }}
+        <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-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
+  </a-form-item>
+  <a-form-item v-if="client.expiryTime != 0">
+    <template slot="label">
+      <a-tooltip>
+        <template slot="title">{{ i18n "pages.client.renewDesc" }}</template>
+        {{ i18n "pages.client.renew" }}
+        <a-icon type="question-circle"></a-icon>
+      </a-tooltip>
+    </template>
+    <a-input-number v-model.number="client.reset" :min="0"></a-input-number>
+  </a-form-item>
 </a-form>
-{{end}}
+{{end}}

+ 48 - 12
web/html/form/stream/external_proxy.html

@@ -1,31 +1,67 @@
 {{define "form/externalProxy"}}
-<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-divider :style="{ margin: '5px 0 0' }"></a-divider>
   <a-form-item label="External Proxy">
     <a-switch v-model="externalProxy"></a-switch>
-    <a-button icon="plus" v-if="externalProxy" type="primary" :style="{ marginLeft: '10px' }" size="small"
-      @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})"></a-button>
+    <a-button
+      icon="plus"
+      v-if="externalProxy"
+      type="primary"
+      :style="{ marginLeft: '10px' }"
+      size="small"
+      @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})"
+    ></a-button>
   </a-form-item>
-  <a-input-group :style="{ margin: '8px 0' }" compact v-for="(row, index) in inbound.stream.externalProxy">
+  <a-input-group
+    :style="{ margin: '8px 0' }"
+    compact
+    v-for="(row, index) in inbound.stream.externalProxy"
+  >
     <template>
       <a-tooltip title="Force TLS">
-        <a-select v-model="row.forceTls" :style="{ width: '20%', margin: '0px' }"
-          :dropdown-class-name="themeSwitcher.currentTheme">
-          <a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
+        <a-select
+          v-model="row.forceTls"
+          :style="{ width: '20%', margin: '0px' }"
+          :dropdown-class-name="themeSwitcher.currentTheme"
+        >
+          <a-select-option value="same"
+            >{{ i18n "pages.inbounds.same" }}</a-select-option
+          >
           <a-select-option value="none">{{ i18n "none" }}</a-select-option>
           <a-select-option value="tls">TLS</a-select-option>
         </a-select>
       </a-tooltip>
     </template>
-    <a-input :style="{ width: '30%' }" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
+    <a-input
+      :style="{ width: '30%' }"
+      v-model.trim="row.dest"
+      placeholder='{{ i18n "host" }}'
+    ></a-input>
     <a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
-      <a-input-number :style="{ width: '15%' }" v-model.number="row.port" min="1" max="65535"></a-input-number>
+      <a-input-number
+        :style="{ width: '15%' }"
+        v-model.number="row.port"
+        min="1"
+        max="65535"
+      ></a-input-number>
     </a-tooltip>
-    <a-input :style="{ width: '30%', top: '0' }" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
+    <a-input
+      :style="{ width: '30%', top: '0' }"
+      v-model.trim="row.remark"
+      placeholder='{{ i18n "remark" }}'
+    >
       <template slot="addonAfter">
-        <a-button icon="minus" size="small" @click="inbound.stream.externalProxy.splice(index, 1)"></a-button>
+        <a-button
+          icon="minus"
+          size="small"
+          @click="inbound.stream.externalProxy.splice(index, 1)"
+        ></a-button>
       </template>
     </a-input>
   </a-input-group>
 </a-form>
-{{end}}
+{{end}}

+ 250 - 165
web/html/form/stream/stream_finalmask.html

@@ -1,167 +1,252 @@
 {{define "form/streamFinalMask"}}
-<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.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"
-            :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)"
-                    :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
-            </a-divider>
-            <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">
-                    <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>
-            <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>
-            <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
+  :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.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"
+      :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)"
+          :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"
+        ></a-icon>
+      </a-divider>
+      <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"
+        >
+          <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>
+      <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>
+      <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}}

+ 15 - 11
web/html/form/stream/stream_grpc.html

@@ -1,13 +1,17 @@
 {{define "form/streamGRPC"}}
-<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
-    <a-form-item label="Service Name">
-        <a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
-    </a-form-item>
-    <a-form-item label="Authority">
-        <a-input v-model.trim="inbound.stream.grpc.authority"></a-input>
-    </a-form-item>
-    <a-form-item label="Multi Mode">
-        <a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
-    </a-form-item>
+<a-form
+  :colon="false"
+  :label-col="{ md: {span:8} }"
+  :wrapper-col="{ md: {span:14} }"
+>
+  <a-form-item label="Service Name">
+    <a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
+  </a-form-item>
+  <a-form-item label="Authority">
+    <a-input v-model.trim="inbound.stream.grpc.authority"></a-input>
+  </a-form-item>
+  <a-form-item label="Multi Mode">
+    <a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
+  </a-form-item>
 </a-form>
-{{end}}
+{{end}}

+ 37 - 12
web/html/form/stream/stream_httpupgrade.html

@@ -1,7 +1,13 @@
 {{define "form/streamHTTPUpgrade"}}
-<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="Proxy Protocol">
-    <a-switch v-model="inbound.stream.httpupgrade.acceptProxyProtocol"></a-switch>
+    <a-switch
+      v-model="inbound.stream.httpupgrade.acceptProxyProtocol"
+    ></a-switch>
   </a-form-item>
   <a-form-item label='{{ i18n "host" }}'>
     <a-input v-model.trim="inbound.stream.httpupgrade.host"></a-input>
@@ -10,20 +16,39 @@
     <a-input v-model.trim="inbound.stream.httpupgrade.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.httpupgrade.addHeader('', '')"></a-button>
+    <a-button
+      icon="plus"
+      size="small"
+      @click="inbound.stream.httpupgrade.addHeader('', '')"
+    ></a-button>
   </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"}}'>
-        <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
+    <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"}}'
+      >
+        <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}}

+ 110 - 57
web/html/form/stream/stream_hysteria.html

@@ -1,62 +1,115 @@
 {{define "form/streamHysteria"}}
-<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-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 "reset" }}</span>
+        </template>
+        Auth Password
+        <a-icon
+          @click="inbound.stream.hysteria.auth = RandomUtil.randomSeq(10)"
+          type="sync"
+        ></a-icon>
+      </a-tooltip>
+    </template>
+    <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-form-item>
+  <a-form-item label="Masquerade">
+    <a-switch v-model="inbound.stream.hysteria.masqueradeSwitch"></a-switch>
+  </a-form-item>
+  <template v-if="inbound.stream.hysteria.masqueradeSwitch">
+    <a-divider :style="{ margin: '5px 0 0' }">Masquerade</a-divider>
+    <a-form-item label="Type">
+      <a-select
+        v-model="inbound.stream.hysteria.masquerade.type"
+        :dropdown-class-name="themeSwitcher.currentTheme"
+      >
+        <a-select-option value="file">File</a-select-option>
+        <a-select-option value="proxy">Proxy</a-select-option>
+        <a-select-option value="string">String</a-select-option>
+      </a-select>
     </a-form-item>
-    <a-form-item label='Masquerade'>
-        <a-switch v-model="inbound.stream.hysteria.masqueradeSwitch"></a-switch>
+    <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.masqueradeSwitch">
-        <a-divider :style="{ margin: '5px 0 0' }">Masquerade</a-divider>
-        <a-form-item label='Type'>
-            <a-select v-model="inbound.stream.hysteria.masquerade.type"
-                :dropdown-class-name="themeSwitcher.currentTheme">
-                <a-select-option value="file">File</a-select-option>
-                <a-select-option value="proxy">Proxy</a-select-option>
-                <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>
-        <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-form-item>
-            <a-form-item label='Rewrite Host'>
-                <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-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-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>
-            <a-form-item :wrapper-col="{span:24}">
-                <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>
-                    </a-input>
-                    <a-input style="width: 50%" v-model.trim="header.value"
-                        placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
-                        <a-button slot="addonAfter" size="small"
-                            @click="inbound.stream.hysteria.masquerade.removeHeader(index)">-</a-button>
-                    </a-input>
-                </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-form-item>
-        </template>
+    <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-form-item>
+      <a-form-item label="Rewrite Host">
+        <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-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-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>
+      <a-form-item :wrapper-col="{span:24}">
+        <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
+            >
+          </a-input>
+          <a-input
+            style="width: 50%"
+            v-model.trim="header.value"
+            placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'
+          >
+            <a-button
+              slot="addonAfter"
+              size="small"
+              @click="inbound.stream.hysteria.masquerade.removeHeader(index)"
+              >-</a-button
+            >
+          </a-input>
+        </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-form-item>
     </template>
+  </template>
 </a-form>
-{{end}}
+{{end}}

+ 47 - 23
web/html/form/stream/stream_kcp.html

@@ -1,25 +1,49 @@
 {{define "form/streamKCP"}}
-<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-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-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-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-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-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-form-item>
+<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-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-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-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-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-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-form-item>
 </a-form>
-{{end}}
+{{end}}

+ 15 - 11
web/html/form/stream/stream_settings.html

@@ -1,10 +1,18 @@
 {{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
+  :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
+      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>
@@ -51,12 +59,8 @@
 </template>
 
 <!-- sockopt -->
-<template>
-  {{template "form/streamSockopt"}}
-</template>
+<template> {{template "form/streamSockopt"}} </template>
 
 <!-- finalmask -->
-<template>
-  {{template "form/streamFinalMask"}}
-</template>
-{{end}}
+<template> {{template "form/streamFinalMask"}} </template>
+{{end}}

+ 116 - 75
web/html/form/stream/stream_sockopt.html

@@ -1,78 +1,119 @@
 {{define "form/streamSockopt"}}
 <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-item label="Sockopt">
-        <a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
-    </a-form-item>
-    <template v-if="inbound.stream.sockoptSwitch">
-        <a-form-item label="Route Mark">
-            <a-input-number v-model.number="inbound.stream.sockopt.mark" :min="0"></a-input-number>
-        </a-form-item>
-        <a-form-item label="TCP Keep Alive Interval">
-            <a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
-        </a-form-item>
-        <a-form-item label="TCP Keep Alive Idle">
-            <a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
-        </a-form-item>
-        <a-form-item label="TCP Max Seg">
-            <a-input-number v-model.number="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
-        </a-form-item>
-        <a-form-item label="TCP User Timeout">
-            <a-input-number v-model.number="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
-        </a-form-item>
-        <a-form-item label="TCP Window Clamp">
-            <a-input-number v-model.number="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
-        </a-form-item>
-        <a-form-item label="Proxy Protocol">
-            <a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
-        </a-form-item>
-        <a-form-item label="TCP Fast Open">
-            <a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
-        </a-form-item>
-        <a-form-item label="Multipath TCP">
-            <a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
-        </a-form-item>
-        <a-form-item label="Penetrate">
-            <a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
-        </a-form-item>
-        <a-form-item label="V6 Only">
-            <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>
-        </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>
-        </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-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>
-            </a-select>
-        </a-form-item>
-        <a-form-item label="Dialer Proxy">
-            <a-input v-model="inbound.stream.sockopt.dialerProxy"></a-input>
-        </a-form-item>
-        <a-form-item label="Interface Name">
-            <a-input v-model="inbound.stream.sockopt.interfaceName"></a-input>
-        </a-form-item>
-        <a-form-item label="Trusted X-Forwarded-For">
-            <a-select mode="tags" v-model="inbound.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="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="X-Client-IP">X-Client-IP</a-select-option>
-            </a-select>
-        </a-form-item>
-    </template>
+<a-form
+  :colon="false"
+  :label-col="{ md: {span:8} }"
+  :wrapper-col="{ md: {span:14} }"
+>
+  <a-form-item label="Sockopt">
+    <a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
+  </a-form-item>
+  <template v-if="inbound.stream.sockoptSwitch">
+    <a-form-item label="Route Mark">
+      <a-input-number
+        v-model.number="inbound.stream.sockopt.mark"
+        :min="0"
+      ></a-input-number>
+    </a-form-item>
+    <a-form-item label="TCP Keep Alive Interval">
+      <a-input-number
+        v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval"
+        :min="0"
+      ></a-input-number>
+    </a-form-item>
+    <a-form-item label="TCP Keep Alive Idle">
+      <a-input-number
+        v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle"
+        :min="0"
+      ></a-input-number>
+    </a-form-item>
+    <a-form-item label="TCP Max Seg">
+      <a-input-number
+        v-model.number="inbound.stream.sockopt.tcpMaxSeg"
+        :min="0"
+      ></a-input-number>
+    </a-form-item>
+    <a-form-item label="TCP User Timeout">
+      <a-input-number
+        v-model.number="inbound.stream.sockopt.tcpUserTimeout"
+        :min="0"
+      ></a-input-number>
+    </a-form-item>
+    <a-form-item label="TCP Window Clamp">
+      <a-input-number
+        v-model.number="inbound.stream.sockopt.tcpWindowClamp"
+        :min="0"
+      ></a-input-number>
+    </a-form-item>
+    <a-form-item label="Proxy Protocol">
+      <a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
+    </a-form-item>
+    <a-form-item label="TCP Fast Open">
+      <a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
+    </a-form-item>
+    <a-form-item label="Multipath TCP">
+      <a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
+    </a-form-item>
+    <a-form-item label="Penetrate">
+      <a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
+    </a-form-item>
+    <a-form-item label="V6 Only">
+      <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>
+    </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>
+    </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-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>
+      </a-select>
+    </a-form-item>
+    <a-form-item label="Dialer Proxy">
+      <a-input v-model="inbound.stream.sockopt.dialerProxy"></a-input>
+    </a-form-item>
+    <a-form-item label="Interface Name">
+      <a-input v-model="inbound.stream.sockopt.interfaceName"></a-input>
+    </a-form-item>
+    <a-form-item label="Trusted X-Forwarded-For">
+      <a-select
+        mode="tags"
+        v-model="inbound.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="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="X-Client-IP">X-Client-IP</a-select-option>
+      </a-select>
+    </a-form-item>
+  </template>
 </a-form>
-{{end}}
+{{end}}

+ 91 - 30
web/html/form/stream/stream_tcp.html

@@ -1,19 +1,31 @@
 {{define "form/streamTCP"}}
 <!-- tcp type -->
-<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="Proxy Protocol" v-if="inbound.canEnableTls()">
     <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-divider :style="{ margin: '0' }"
+    >{{ i18n "pages.inbounds.stream.general.request" }}</a-divider
+  >
   <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
     <a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
   </a-form-item>
@@ -21,35 +33,66 @@
     <a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
   </a-form-item>
   <a-form-item>
-    <template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }}
-      <a-button icon="plus" size="small" @click="inbound.stream.tcp.request.addPath('/')"></a-button>
+    <template slot="label"
+      >{{ i18n "pages.inbounds.stream.tcp.path" }}
+      <a-button
+        icon="plus"
+        size="small"
+        @click="inbound.stream.tcp.request.addPath('/')"
+      ></a-button>
     </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>
   <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
-    <a-button icon="plus" size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')"></a-button>
+    <a-button
+      icon="plus"
+      size="small"
+      @click="inbound.stream.tcp.request.addHeader('Host', '')"
+    ></a-button>
   </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" }}'>
-        <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
+    <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" }}'
+      >
+        <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>
 
   <!-- tcp response -->
-  <a-divider :style="{ margin: '0' }">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider>
+  <a-divider :style="{ margin: '0' }"
+    >{{ i18n "pages.inbounds.stream.general.response" }}</a-divider
+  >
   <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
     <a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
   </a-form-item>
@@ -60,22 +103,40 @@
     <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" }}'>
-        <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
+    <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" }}'
+      >
+        <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>
+          <a-button
+            icon="minus"
+            size="small"
+            @click="inbound.stream.tcp.response.removeHeader(index)"
+          ></a-button>
         </template>
       </a-input>
     </a-input-group>
   </a-form-item>
 </a-form>
-{{end}}
+{{end}}

+ 35 - 11
web/html/form/stream/stream_ws.html

@@ -1,5 +1,9 @@
 {{define "form/streamWS"}}
-<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="Proxy Protocol">
     <a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
   </a-form-item>
@@ -9,23 +13,43 @@
   <a-form-item label='{{ i18n "path" }}'>
     <a-input v-model.trim="inbound.stream.ws.path"></a-input>
   </a-form-item>
-  <a-form-item label='Heartbeat Period'>
-    <a-input-number v-model.number="inbound.stream.ws.heartbeatPeriod" :min="0"></a-input-number>
+  <a-form-item label="Heartbeat Period">
+    <a-input-number
+      v-model.number="inbound.stream.ws.heartbeatPeriod"
+      :min="0"
+    ></a-input-number>
   </a-form-item>
   <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
-    <a-button icon="plus" size="small" @click="inbound.stream.ws.addHeader('', '')"></a-button>
+    <a-button
+      icon="plus"
+      size="small"
+      @click="inbound.stream.ws.addHeader('', '')"
+    ></a-button>
   </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"}}'>
-        <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
+      <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.ws.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.ws.removeHeader(index)"
+        ></a-button>
       </a-input>
     </a-input-group>
   </a-form-item>
 </a-form>
-{{end}}
+{{end}}

+ 210 - 127
web/html/form/stream/stream_xhttp.html

@@ -1,129 +1,212 @@
 {{define "form/streamXHTTP"}}
-<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>
-    <a-form-item label='{{ i18n "path" }}'>
-        <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-form-item>
-    <a-form-item :wrapper-col="{span:24}">
-        <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
-                    ]]</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.xhttp.removeHeader(index)"></a-button>
-            </a-input>
-        </a-input-group>
-    </a-form-item>
-    <a-form-item label='Mode'>
-        <a-select v-model="inbound.stream.xhttp.mode" :style="{ width: '50%' }"
-            :dropdown-class-name="themeSwitcher.currentTheme">
-            <a-select-option v-for="key in MODE_OPTION" :value="key">[[ key
-                ]]</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>
-    <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>
-    <a-form-item label="Padding Bytes">
-        <a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
-    </a-form-item>
-    <a-form-item label="Padding Obfs Mode">
-        <a-switch v-model="inbound.stream.xhttp.xPaddingObfsMode"></a-switch>
-    </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-form-item>
-        <a-form-item label="Padding Header">
-            <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="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-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>
-            </a-select>
-        </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-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>
-            <a-select-option value="GET">GET (packet-up only)</a-select-option>
-        </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-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>
-            <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="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-form-item>
-    <a-form-item label="Sequence Placement">
-        <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>
-            <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="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-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-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>
-            <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="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-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-form-item>
-    <a-form-item label="No SSE Header">
-        <a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
-    </a-form-item>
+<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>
+  <a-form-item label='{{ i18n "path" }}'>
+    <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-form-item>
+  <a-form-item :wrapper-col="{span:24}">
+    <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 ]]</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.xhttp.removeHeader(index)"
+        ></a-button>
+      </a-input>
+    </a-input-group>
+  </a-form-item>
+  <a-form-item label="Mode">
+    <a-select
+      v-model="inbound.stream.xhttp.mode"
+      :style="{ width: '50%' }"
+      :dropdown-class-name="themeSwitcher.currentTheme"
+    >
+      <a-select-option v-for="key in MODE_OPTION" :value="key"
+        >[[ key ]]</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>
+  <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>
+  <a-form-item label="Padding Bytes">
+    <a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
+  </a-form-item>
+  <a-form-item label="Padding Obfs Mode">
+    <a-switch v-model="inbound.stream.xhttp.xPaddingObfsMode"></a-switch>
+  </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-form-item>
+    <a-form-item label="Padding Header">
+      <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="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-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>
+      </a-select>
+    </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-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>
+      <a-select-option value="GET">GET (packet-up only)</a-select-option>
+    </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-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>
+      <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="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-form-item>
+  <a-form-item label="Sequence Placement">
+    <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>
+      <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="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-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-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>
+      <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="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-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-form-item>
+  <a-form-item label="No SSE Header">
+    <a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
+  </a-form-item>
 </a-form>
-{{end}}
+{{end}}