Forráskód Böngészése

[gui] redesign forms

Co-Authored-By: Alireza Ahmadi <[email protected]>
MHSanaei 1 éve
szülő
commit
8d18c8e98f
32 módosított fájl, 1442 hozzáadás és 2385 törlés
  1. 12 58
      web/assets/css/custom.css
  2. 5 5
      web/assets/js/model/outbound.js
  3. 7 7
      web/assets/js/model/xray.js
  4. 5 0
      web/html/login.html
  5. 117 200
      web/html/xui/client_bulk_modal.html
  6. 168 218
      web/html/xui/form/client.html
  7. 55 84
      web/html/xui/form/inbound.html
  8. 222 459
      web/html/xui/form/outbound.html
  9. 17 39
      web/html/xui/form/protocol/dokodemo.html
  10. 1 1
      web/html/xui/form/protocol/http.html
  11. 36 38
      web/html/xui/form/protocol/shadowsocks.html
  12. 30 49
      web/html/xui/form/protocol/socks.html
  13. 39 67
      web/html/xui/form/protocol/trojan.html
  14. 42 70
      web/html/xui/form/protocol/vless.html
  15. 19 21
      web/html/xui/form/protocol/vmess.html
  16. 19 18
      web/html/xui/form/sniffing.html
  17. 20 26
      web/html/xui/form/stream/external_proxy.html
  18. 7 19
      web/html/xui/form/stream/stream_grpc.html
  19. 16 21
      web/html/xui/form/stream/stream_http.html
  20. 35 83
      web/html/xui/form/stream/stream_kcp.html
  21. 21 40
      web/html/xui/form/stream/stream_quic.html
  22. 3 4
      web/html/xui/form/stream/stream_settings.html
  23. 20 40
      web/html/xui/form/stream/stream_sockopt.html
  24. 61 98
      web/html/xui/form/stream/stream_tcp.html
  25. 11 18
      web/html/xui/form/stream/stream_ws.html
  26. 182 382
      web/html/xui/form/tls_settings.html
  27. 158 133
      web/html/xui/inbound_info_modal.html
  28. 5 0
      web/html/xui/inbound_modal.html
  29. 4 0
      web/html/xui/inbounds.html
  30. 4 1
      web/html/xui/xray.html
  31. 33 69
      web/html/xui/xray_reverse_modal.html
  32. 68 117
      web/html/xui/xray_rule_modal.html

+ 12 - 58
web/assets/css/custom.css

@@ -55,7 +55,7 @@ style attribute {
 }
 .ant-table-tbody > tr > td,
 .ant-table-thead > tr > th {
-    padding: 12px 16px;
+    padding: 12px 8px;
     overflow-wrap: break-word;
 }
 .ant-table-thead > tr > th {
@@ -93,7 +93,6 @@ style attribute {
 .ant-table-body {
     overflow-x: auto !important;
 }
-
 .ant-card-hoverable {
     cursor: auto;
     cursor: pointer;
@@ -133,6 +132,13 @@ style attribute {
         margin: 0.5rem;
         padding: 0.5rem;
     }
+    .ant-modal-body {
+        padding: 10px;
+    }
+    .ant-form-item-label {
+        line-height: 1.5;
+        padding: 8px 0 0;
+    }
 }
 
 .ant-layout-content {
@@ -410,6 +416,10 @@ style attribute {
     background-color: white;
 }
 
+.ant-form-item {
+    margin-bottom: 0;
+}
+
 .ant-setting-textarea {
     margin-top: 1.5rem;
 }
@@ -802,12 +812,6 @@ style attribute {
     border-color: #fec093;
 }
 
-.ant-modal-confirm-confirm .ant-modal-confirm-body>.anticon, .ant-modal-confirm-warning .ant-modal-confirm-body>.anticon,
-.ant-alert-warning .ant-alert-icon,
-.has-warning.has-feedback .ant-form-item-children-icon {
-    color: #f37b24;
-}
-
 .dark .has-warning .ant-input,
 .dark .has-warning .ant-input:hover {
     border-color: #784e1d;
@@ -1045,53 +1049,3 @@ li.ant-select-dropdown-menu-item:empty:after {
 .ant-input-number {
     overflow: clip;
 }
-
-.tag-of-wrap {
-	text-wrap: pretty;
-	overflow-wrap: anywhere;
-	max-width: 200px;
-}
-
-.tag-of-wrap-l {
-	text-wrap: pretty;
-	overflow-wrap: anywhere;
-	max-width: 350px;
-}
-
-.ant-modal-body,
-.ant-collapse-content>.ant-collapse-content-box {
-    overflow-x: auto;
-}
-
-.ant-calendar-year-panel-year:hover,
-.ant-calendar-decade-panel-decade:hover,
-.ant-calendar-month-panel-month:hover,
-.ant-dropdown-menu-item:hover,
-.ant-dropdown-menu-submenu-title:hover,
-.ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
-.ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
-.ant-table-tbody
-    > tr.ant-table-row-hover:not(.ant-table-expanded-row):not(
-        .ant-table-row-selected
-    )
-    > td,
-.ant-table-tbody
-    > tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
-    > td,
-.ant-table-thead
-    > tr.ant-table-row-hover:not(.ant-table-expanded-row):not(
-        .ant-table-row-selected
-    )
-    > td,
-.ant-table-thead
-    > tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
-    > td {
-    background-color: rgb(232 244 242);
-}
-
-.dark .ant-dropdown-menu-item:hover,
-.dark .ant-dropdown-menu-submenu-title:hover,
-.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
-.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
-    background-color: #313f5a;
-}

+ 5 - 5
web/assets/js/model/outbound.js

@@ -476,7 +476,7 @@ class Outbound extends CommonClass {
         if(data.length !=2) return null;
         switch(data[0].toLowerCase()){
             case Protocols.VMess:
-                return this.fromVmessLink(JSON.parse(atob(data[1])));
+                return this.fromVmessLink(JSON.parse(Base64.decode(data[1])));
             case Protocols.VLESS:
             case Protocols.Trojan:
             case 'ss':
@@ -493,8 +493,8 @@ class Outbound extends CommonClass {
         if (network === 'tcp') {
             stream.tcp = new TcpStreamSettings(
                 json.type,
-                json.host ? json.host.split(','): [],
-                json.path ? json.path.split(','): []);
+                json.host ?? '',
+                json.path ?? '');
         } else if (network === 'kcp') {
             stream.kcp = new KcpStreamSettings();
             stream.type = json.type;
@@ -505,7 +505,7 @@ class Outbound extends CommonClass {
             stream.network = 'http'
             stream.http = new HttpStreamSettings(
                 json.path,
-                json.host ? json.host.split(',') : []);
+                json.host);
         } else if (network === 'quic') {
             stream.quic = new QuicStreamSettings(
                 json.host ? json.host : 'none',
@@ -570,7 +570,7 @@ class Outbound extends CommonClass {
             let sni=url.searchParams.get('sni') ?? '';
             let sid=url.searchParams.get('sid') ?? '';
             let spx=url.searchParams.get('spx') ?? '';
-            stream.tls = new RealityStreamSettings(pbk, fp, sni, sid, spx);
+            stream.reality  = new RealityStreamSettings(pbk, fp, sni, sid, spx);
         }
 
         let data = link.split('?');

+ 7 - 7
web/assets/js/model/xray.js

@@ -602,7 +602,7 @@ class XtlsStreamSettings extends XrayCommonClass {
                 alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
                 settings=new XtlsStreamSettings.Settings()) {
         super();
-        this.server = serverName;
+        this.sni = serverName;
         this.certs = certificates;
         this.alpn = alpn;
         this.settings = settings;
@@ -636,7 +636,7 @@ class XtlsStreamSettings extends XrayCommonClass {
 
     toJson() {
         return {
-            serverName: this.server,
+            serverName: this.sni,
             certificates: XtlsStreamSettings.toJsonArray(this.certs),
             alpn: this.alpn,
             settings: this.settings,
@@ -1081,7 +1081,7 @@ class Inbound extends XrayCommonClass {
 
     get serverName() {
         if (this.stream.isTls) return this.stream.tls.sni;
-        if (this.stream.isXtls) return this.stream.xtls.server;
+        if (this.stream.isXtls) return this.stream.xtls.sni;
         if (this.stream.isReality) return this.stream.reality.serverNames;
         return "";
     }
@@ -1326,8 +1326,8 @@ class Inbound extends XrayCommonClass {
             if(this.stream.xtls.settings.allowInsecure){
                 params.set("allowInsecure", "1");
             }
-            if (!ObjectUtil.isEmpty(this.stream.xtls.server)){
-                params.set("sni", this.stream.xtls.server);
+            if (!ObjectUtil.isEmpty(this.stream.xtls.sni)){
+                params.set("sni", this.stream.xtls.sni);
 			}
             params.set("flow", flow);
         }
@@ -1533,8 +1533,8 @@ class Inbound extends XrayCommonClass {
             if(this.stream.xtls.settings.allowInsecure){
                 params.set("allowInsecure", "1");
             }
-            if (this.stream.xtls.settings.serverName !== ''){
-                params.set("sni", this.stream.xtls.settings.serverName);
+            if (!ObjectUtil.isEmpty(this.stream.xtls.sni)){
+                params.set("sni", this.stream.xtls.sni);
 			}
             params.set("flow", flow);
         }

+ 5 - 0
web/html/login.html

@@ -27,6 +27,7 @@
     text-align: center;
     align-items: center;
     justify-content: center;
+    width: 100%;
   }
   .title {
     font-size: 32px;
@@ -92,6 +93,10 @@
   .dark h1 {
     color: rgba(255, 255, 255, 0.85);
   }
+  .ant-form-item {
+    margin-bottom: 16px;
+  }
+
   .ant-btn-primary-login {
     color: #008771;
     background-color: #e8f4f2;

+ 117 - 200
web/html/xui/client_bulk_modal.html

@@ -2,206 +2,123 @@
 <a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title"
     @ok="clientsBulkModal.ok" :confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
     :ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
-    <a-form layout="inline">
-        <table width="100%" class="ant-table-tbody">
-            <tr>
-                <td>{{ i18n "pages.client.method" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
-                            :dropdown-class-name="themeSwitcher.currentTheme">
-                            <a-select-option :value="0">Random</a-select-option>
-                            <a-select-option :value="1">Random+Prefix</a-select-option>
-                            <a-select-option :value="2">Random+Prefix+Num</a-select-option>
-                            <a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
-                            <a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
-                        </a-select>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="clientsBulkModal.emailMethod>1">
-                <td>{{ i18n "pages.client.first" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="clientsBulkModal.emailMethod>1">
-                <td>{{ i18n "pages.client.last" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model="clientsBulkModal.lastNum"
-                            :min="clientsBulkModal.firstNum"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="clientsBulkModal.emailMethod>0">
-                <td>{{ i18n "pages.client.prefix" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="clientsBulkModal.emailMethod>2">
-                <td>{{ i18n "pages.client.postfix" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="clientsBulkModal.emailMethod < 2">
-                <td>{{ i18n "pages.client.clientCount" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
-                <td>Flow</td>
-                <td>
-                    <a-form-item>
-                        <a-select v-model="clientsBulkModal.flow" style="width: 250px"
-                            :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>
-                </td>
-            </tr>
-            <tr v-if="app.subSettings.enable">
-                <td>Subscription
-                    <a-tooltip>
-                        <template slot="title">
-                            <span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
-                        </template>
-                        <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
-                    </a-tooltip>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="app.tgBotEnable">
-                <td>Telegram ID
-                    <a-tooltip>
-                        <template slot="title">
-                            <span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
-                        </template>
-                        <a-icon type="question-circle" theme="filled"></a-icon>
-                    </a-tooltip>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <label>
-                        <span>{{ i18n "pages.inbounds.IPLimit" }}</span>
-                        <a-tooltip>
-                            <template slot="title">
-                                <span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
-                            </template>
-                            <a-icon type="question-circle" theme="filled"></a-icon>
-                        </a-tooltip>
-                    </label>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td v-if="clientsBulkModal.inbound.xtls">
-                    <label>Flow</label>
-                </td>
-                <td v-if="clientsBulkModal.inbound.xtls">
-                    <a-form-item>
-                        <a-select v-model="clientsBulkModal.flow" style="width: 200px"
-                            :dropdown-class-name="themeSwitcher.currentTheme">
-                            <a-select-option value="">{{ i18n "none" }}</a-select-option>
-                            <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
-                        </a-select>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
-                    <a-tooltip>
-                        <template slot="title">
-                            0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
-                        </template>
-                        <a-icon type="question-circle" theme="filled"></a-icon>
-                    </a-tooltip>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>{{ i18n "pages.client.delayedStart" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-switch v-model="clientsBulkModal.delayedStart"
-                            @click="clientsBulkModal.expiryTime=0"></a-switch>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="clientsBulkModal.delayedStart">
-                <td>{{ i18n "pages.client.expireDays" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-else>
-                <td>
-                    <span>{{ i18n "pages.inbounds.expireDate" }}</span>
-                    <a-tooltip>
-                        <template slot="title">
-                            <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
-                        </template>
-                        <a-icon type="question-circle" theme="filled"></a-icon>
-                    </a-tooltip>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
-                            :dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"
-                            style="width: 250px;"></a-date-picker>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr v-if="clientsBulkModal.expiryTime != 0">
-                <td>
-                    <span>{{ i18n "pages.client.renew" }}</span>
-                    <a-tooltip>
-                        <template slot="title">
-                            <span>{{ i18n "pages.client.renewDesc" }}</span>
-                        </template>
-                        <a-icon type="question-circle" theme="filled"></a-icon>
-                    </a-tooltip>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-        </table>
+    <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
+        <a-form-item label='{{ i18n "pages.client.method" }}'>
+            <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid"
+                :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option :value="0">Random</a-select-option>
+                <a-select-option :value="1">Random+Prefix</a-select-option>
+                <a-select-option :value="2">Random+Prefix+Num</a-select-option>
+                <a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
+                <a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
+            <a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
+            <a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
+            <a-input v-model="clientsBulkModal.emailPrefix"></a-input>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.client.postfix" }}' v-if="clientsBulkModal.emailMethod>2">
+            <a-input v-model="clientsBulkModal.emailPostfix"></a-input>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
+            <a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
+        </a-form-item>
+        <a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
+            <a-select v-model="clientsBulkModal.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 label='Flow' v-if="clientsBulkModal.inbound.xtls">
+            <a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
+                <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item v-if="app.subSettings.enable">
+            <template slot="label">
+                <a-tooltip>
+                    <template slot="title">
+                        <span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
+                    </template>
+                    Subscription
+                    <a-icon @click="clientsBulkModal.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
+                </a-tooltip>
+            </template>
+            <a-input v-model.trim="clientsBulkModal.subId"></a-input>
+        </a-form-item>
+        <a-form-item v-if="app.tgBotEnable">
+            <template slot="label">
+                <a-tooltip>
+                    <template slot="title">
+                        <span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
+                    </template>
+                    Telegram ID
+                    <a-icon type="question-circle" theme="filled"></a-icon>
+                </a-tooltip>
+            </template>
+            <a-input v-model.trim="clientsBulkModal.tgId"></a-input>
+        </a-form-item>
+        <a-form-item>
+            <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" theme="filled"></a-icon>
+                </a-tooltip>
+            </template>
+            <a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number>
+        </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" }} (GB)
+                    <a-icon type="question-circle" theme="filled"></a-icon>
+                </a-tooltip>
+            </template>
+            <a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
+            <a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
+            <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">
+                        <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
+                    </template>
+                    {{ i18n "pages.inbounds.expireDate" }}
+                    <a-icon type="question-circle" theme="filled"></a-icon>
+                </a-tooltip>
+            </template>
+            <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
+                :dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker>
+        </a-form-item>
+        <a-form-item v-if="clientsBulkModal.expiryTime != 0">
+            <template slot="label">
+                <span>{{ i18n "pages.client.renew" }}</span>
+                <a-tooltip>
+                    <template slot="title">
+                        <span>{{ i18n "pages.client.renewDesc" }}</span>
+                    </template>
+                    <a-icon type="question-circle" theme="filled"></a-icon>
+                </a-tooltip>
+            </template>
+            <a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
+        </a-form-item>
     </a-form>
 </a-modal>
 <script>

+ 168 - 218
web/html/xui/form/client.html

@@ -1,223 +1,173 @@
 {{define "form/client"}}
-<a-form layout="inline" v-if="client">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "pages.inbounds.enable" }}</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="client.enable"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>
-                <span>{{ i18n "pages.inbounds.email" }}</span>
-                <a-tooltip>
-                    <template slot="title">
-                        <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
-                    </template>
-                    <a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="client.email" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
-            <td>password
-                <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
-                <a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="client.password" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
-            <td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
-            <td>
-                <a-input v-model.trim="client.id" style="width: 250px;"></a-input>
-            </td>
-        </tr>
-        <tr v-if="client.email && app.subSettings.enable">
-            <td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
-            <td>
-                <a-tooltip>
-                    <template slot="title">
-                        <span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
-                    </template>
-                </a-tooltip>
-                <a-form-item>
-                    <a-input v-model.trim="client.subId" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="client.email && app.tgBotEnable">
-            <td>Telegram ID
-                <a-tooltip>
-                    <template slot="title">
-                        <span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
-                    </template>
-                    <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>
-                <span>{{ i18n "pages.inbounds.IPLimit" }}</span>
-                <a-tooltip>
-                    <template slot="title">
-                        <span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
-                    </template>
-                    <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model="client.limitIp" min="0"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="client.email && client.limitIp > 0 && isEdit">
-            <td>
-                <span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
-                <a-tooltip>
-                    <template slot="title">
-                        <span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
-                    </template>
-                    <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-                <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>
-            </td>
-            <td>
-                <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>
-            </td>
-        </tr>
-        <tr v-if="inbound.xtls">
-            <td>Flow</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="client.flow" style="width: 200px"
-                        :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option value="">{{ i18n "none" }}</a-select-option>
-                        <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-else-if="inbound.canEnableTlsFlow()">
-            <td>Flow</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="client.flow" style="width: 200px"
-                        :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>
-            </td>
-        </tr>
-        <tr>
-            <td>
-                <span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
-                <a-tooltip>
-                    <template slot="title">
-                        0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
-                    </template>
-                    <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model="client._totalGB" :min="0"></a-input-number>
-                </a-form-item>
-                <template v-if="isEdit && clientStats">
-                    <br>
-                    <span> {{ i18n "usage" }}:</span>
-                    <a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
-                        [[ sizeFormat(clientStats.up) ]] /
-                        [[ sizeFormat(clientStats.down) ]]
-                        ([[ 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 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>
+        </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 "pages.client.renew" }}</span>
+                </template>
+                {{ i18n "password" }}
+                <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS"@click="client.password = RandomUtil.randomShadowsocksPassword()" 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.VMESS || inbound.protocol === Protocols.VLESS">
+        <template slot="label">
+            <a-tooltip>
+                <template slot="title">
+                    <span>{{ i18n "pages.client.renew" }}</span>
+                </template>
+                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="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>
+        </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 ID
+                <a-icon type="question-circle"></a-icon>
+            </a-tooltip>
+        </template>
+        <a-input v-model.trim="client.tgId"></a-input>
+    </a-form-item>
+    <a-form-item>
+        <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" theme="filled"></a-icon>
+            </a-tooltip>
+        </template>
+        <a-input-number v-model="client.limitIp" min="0"></a-input-number>
+    </a-form-item>
+    <a-form-item v-if="client.email && client.limitIp > 0 && 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" theme="filled"></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.xtls" 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 XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
+        </a-select>
+    </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" }}(GB)
+                <a-icon type="question-circle"></a-icon>
+            </a-tooltip>
+        </template>
+        <a-input-number v-model="client._totalGB" :min="0"></a-input-number>
+    </a-form-item>
+    <a-form-item v-if="isEdit && clientStats">
+        {{ i18n "usage" }}
+        <a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
+            [[ sizeFormat(clientStats.up) ]] /
+            [[ sizeFormat(clientStats.down) ]]
+            ([[ 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">
+                    <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
                 </template>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.client.delayedStart" }}</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="delayedStart">
-            <td>{{ i18n "pages.client.expireDays" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model="delayedExpireDays" :min="0"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-else>
-            <td>
                 <span>{{ i18n "pages.inbounds.expireDate" }}</span>
-                <a-tooltip>
-                    <template slot="title">
-                        <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
-                    </template>
-                    <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
-                        :dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"
-                        style="width: 170px;"></a-date-picker>
-                    <a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="client.expiryTime != 0">
-            <td>
-                <span>{{ i18n "pages.client.renew" }}</span>
-                <a-tooltip>
-                    <template slot="title">
-                        <span>{{ i18n "pages.client.renewDesc" }}</span>
-                    </template>
-                    <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="client.reset" :min="0"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+                <a-icon type="question-circle" theme="filled"></a-icon>
+            </a-tooltip>
+        </template>
+        <a-date-picker :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-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">
+                    <span>{{ i18n "pages.client.renewDesc" }}</span>
+                </template>
+                {{ i18n "pages.client.renew" }}
+                <a-icon type="question-circle" theme="filled"></a-icon>
+            </a-tooltip>
+        </template>
+        <a-input-number v-model.number="client.reset" :min="0"></a-input-number>
+    </a-form-item>
 </a-form>
 {{end}}

+ 55 - 84
web/html/xui/form/inbound.html

@@ -1,93 +1,64 @@
 {{define "form/inbound"}}
 <!-- base -->
-<a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "enable" }}</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="dbInbound.enable"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "remark" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "protocol" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="inbound.protocol" style="width: 250px;" :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>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "monitor" }}
-                <a-tooltip>
+<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label='{{ i18n "enable" }}'>
+        <a-switch v-model="dbInbound.enable"></a-switch>
+    </a-form-item>
+    <a-form-item label='{{ i18n "remark" }}'>
+        <a-input v-model.trim="dbInbound.remark"></a-input>
+    </a-form-item>
+
+    <a-form-item label='{{ i18n "protocol" }}'>
+        <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>
+    </a-form-item>
+
+    <a-form-item>
+        <template slot="label">
+            <a-tooltip>
                 <template slot="title">
                     <span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
                 </template>
-                <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.port" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="inbound.port"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>
-                <span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
-                <a-tooltip>
-                    <template slot="title">
-                        0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
-                    </template>
-                    <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>
-                <span>{{ i18n "pages.inbounds.expireDate" }}</span>
-                <a-tooltip>
-                    <template slot="title">
-                        <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
-                    </template>
-                    <a-icon type="question-circle" theme="filled"></a-icon>
-                </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
-                                   :dropdown-class-name="themeSwitcher.currentTheme"
-                                   v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
-</a-form>
+                {{ i18n "monitor" }}
+                <a-icon type="question-circle"></a-icon>
+            </a-tooltip>
+        </template>
+        <a-input v-model.trim="inbound.listen"></a-input>
+    </a-form-item>
+
+    <a-form-item label='{{ i18n "pages.inbounds.port" }}'>
+        <a-input-number v-model.number="inbound.port"></a-input-number>
+    </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" }}(GB)
+                <a-icon type="question-circle"></a-icon>
+            </a-tooltip>
+        </template>
+        <a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
+    </a-form-item>
 
+    <a-form-item>
+        <template slot="label">
+            <a-tooltip>
+                <template slot="title">
+                    <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
+                </template>
+                {{ i18n "pages.inbounds.expireDate" }}
+                <a-icon type="question-circle"></a-icon>
+            </a-tooltip>
+        </template>
+            <a-date-picker :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-form-item>
+</a-form>
 
 <!-- vmess settings -->
 <template v-if="inbound.protocol === Protocols.VMESS">

+ 222 - 459
web/html/xui/form/outbound.html

@@ -2,535 +2,298 @@
 <!-- base -->
 <a-tabs :active-key="outModal.activeKey"  style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
 <a-tab-pane key="1" tab="Form">
-<a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "protocol" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="outbound.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.xray.outbound.tag" }}</td>
-            <td>
-                <a-form-item has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
-                    <a-input v-model.trim="outbound.tag" style="width: 250px" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
-                </a-form-item>
-            </td>
-        </tr>
+<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-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
+            <a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
+        </a-form-item>
 
 <!-- freedom settings-->
 <template v-if="outbound.protocol === Protocols.Freedom">
-        <tr>
-            <td>Strategy</td>
-            <td>
-                <a-form-item>
-                    <a-select
-                    v-model="outbound.settings.domainStrategy"
-                    style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Fragment</td>
-            <td>
-                <a-form-item>
-                    <a-switch
-                        :checked="Object.keys(outbound.settings.fragment).length >0"
-                        @change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
-                    </a-switch>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='Strategy'>
+            <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>
+        </a-form-item>
+        <a-form-item label='Fragment'>
+            <a-switch
+                :checked="Object.keys(outbound.settings.fragment).length >0"
+                @change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
+            </a-switch>
+        </a-form-item>
     <template v-if="Object.keys(outbound.settings.fragment).length >0">
-        <tr>
-            <td>Packets</td>
-            <td>
-                <a-form-item>
-                    <a-select
-                    v-model="outbound.settings.fragment.packets"
-                    style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Length</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.settings.fragment.length" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Interval</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.settings.fragment.interval" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='Packets'>
+            <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>
+        </a-form-item>
+        <a-form-item label='Length'>
+            <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-form-item>
     </template>
 </template>
 
 <!-- blackhole settings -->
 <template v-if="outbound.protocol === Protocols.Blackhole">
-        <tr>
-            <td>Response Type</td>
-            <td>
-                <a-form-item>
-                    <a-select
-                    v-model="outbound.settings.type"
-                    style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='Response Type'>
+            <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>
+        </a-form-item>
 </template>
 
 <!-- dns settings -->
 <template v-if="outbound.protocol === Protocols.DNS">
-        <tr>
-            <td>{{ i18n "pages.inbounds.network" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select
-                    v-model="outbound.settings.network"
-                    style="width: 250px;" :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>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "pages.inbounds.network" }}'>
+            <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>
 </template>
 
 <!-- Address + Port -->
 <template v-if="outbound.hasAddressPort()">
-        <tr>
-            <td>{{ i18n "pages.inbounds.address" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.settings.address" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.port" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "pages.inbounds.address" }}'>
+            <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-form-item>
 </template>
 
 <!-- Vnext (vless/vmess) settings -->
 <template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
-        <tr>
-            <td>ID</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.settings.id" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='ID'>
+            <a-input v-model.trim="outbound.settings.id"></a-input>
+        </a-form-item>
     <!-- vless settings -->
     <template v-if="outbound.canEnableTlsFlow()">
-        <tr>
-            <td>Flow</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="outbound.settings.flow" style="width: 250px" :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>
-            </td>
-        </tr>
+        <a-form-item label='Flow'>
+            <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">[[ key ]]</a-select-option>
+            </a-select>
+        </a-form-item>
     </template>
 </template>
 
 <!-- Servers (trojan/shadowsocks/socks/http) settings -->
 <template v-if="outbound.hasServers()">
-        <tr v-if="outbound.hasUsername()">
-            <td>{{ i18n "username" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.settings.user" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "password" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.settings.password" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
+    <template v-if="outbound.hasUsername()">
+        <a-form-item label='{{ i18n "username" }}'>
+            <a-input v-model.trim="outbound.settings.user"></a-input>
+        </a-form-item>
+        <a-form-item label='{{ i18n "password" }}'>
+            <a-input v-model.trim="outbound.settings.password"></a-input>
+        </a-form-item>
+    </template>
     <!-- shadowsocks -->
     <template v-if="outbound.protocol === Protocols.Shadowsocks">
-        <tr>
-            <td>{{ i18n "encryption" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="outbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>UDP over TCP</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="outbound.settings.uot"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "encryption" }}'>
+            <a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='UDP over TCP'>
+            <a-switch v-model="outbound.settings.uot"></a-switch>
+        </a-form-item>
     </template>
 </template>
 
 <!-- stream settings -->
 <template v-if="outbound.canEnableStream()">
-        <tr>
-            <td>{{ i18n "transmission" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="outbound.stream.network" @change="streamNetworkChange"
-                    style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option value="tcp">TCP</a-select-option>
-                        <a-select-option value="kcp">KCP</a-select-option>
-                        <a-select-option value="ws">WebSocket</a-select-option>
-                        <a-select-option value="http">HTTP2</a-select-option>
-                        <a-select-option value="quic">QUIC</a-select-option>
-                        <a-select-option value="grpc">gRPC</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "transmission" }}'>
+            <a-select v-model="outbound.stream.network" @change="streamNetworkChange"
+                    :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option value="tcp">TCP</a-select-option>
+                <a-select-option value="kcp">KCP</a-select-option>
+                <a-select-option value="ws">WS</a-select-option>
+                <a-select-option value="http">HTTP2</a-select-option>
+                <a-select-option value="quic">QUIC</a-select-option>
+                <a-select-option value="grpc">gRPC</a-select-option>
+            </a-select>
+        </a-form-item>
     <template v-if="outbound.stream.network === 'tcp'">
-        <tr>
-            <td>http {{ i18n "camouflage" }}</td>
-            <td>
-                <a-form-item>
-                    <a-switch
-                            :checked="outbound.stream.tcp.type === 'http'"
-                            @change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
-                    </a-switch>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='HTTP {{ i18n "camouflage" }}'>
+            <a-switch
+                :checked="outbound.stream.tcp.type === 'http'"
+                @change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
+            </a-switch>
+        </a-form-item>
         <template v-if="outbound.stream.tcp.type == 'http'">
-        <tr>
-            <td>{{ i18n "host" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.host"></a-input>
-                </a-form-item> 
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "path" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.path"></a-input>
-                </a-form-item> 
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "host" }}'>
+            <a-input v-model.trim="outbound.stream.tcp.host"></a-input>
+        </a-form-item> 
+        <a-form-item label='{{ i18n "path" }}'>
+            <a-input v-model.trim="outbound.stream.tcp.path"></a-input>
+        </a-form-item> 
         </template>
     </template>
     
     <!-- kcp -->
     <template v-if="outbound.stream.network === 'kcp'">
-        <tr>
-            <td>{{ i18n "camouflage" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="outbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option value="none">none (not camouflage)</a-select-option>
-                        <a-select-option value="srtp">srtp (video call)</a-select-option>
-                        <a-select-option value="utp">utp (BT download)</a-select-option>
-                        <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
-                        <a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
-                        <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "password" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model="outbound.stream.kcp.seed" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>mtu</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>tti (ms)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>uplink capacity (MB/S)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
-                </a-form-item> 
-            </td>
-        </tr>
-        <tr>
-            <td>downlink capacity (MB/S)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>congestion</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>read buffer size (MB)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>write buffer size (MB)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "camouflage" }}'>
+            <a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option value="none">none (not camouflage)</a-select-option>
+                <a-select-option value="srtp">srtp (video call)</a-select-option>
+                <a-select-option value="utp">utp (BT download)</a-select-option>
+                <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
+                <a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
+                <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='{{ i18n "password" }}'>
+            <a-input v-model="outbound.stream.kcp.seed"></a-input>
+        </a-form-item>
+        <a-form-item label='mtu'>
+            <a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
+        </a-form-item>
+        <a-form-item label='tti (ms)'>
+            <a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
+        </a-form-item>
+        <a-form-item label='uplink capacity (MB/S)'>
+            <a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
+        </a-form-item> 
+        <a-form-item label='downlink capacity (MB/S)'>
+            <a-input-number v-model.number="outbound.stream.kcp.downCap"></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 size (MB)'>
+            <a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
+        </a-form-item>
+        <a-form-item label='write buffer size (MB)'>
+            <a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
+        </a-form-item>
     </template>
     
     <!-- ws -->
     <template v-if="outbound.stream.network === 'ws'">
-        <tr>
-            <td>{{ i18n "host" }}</td>
-            <td><a-form-item><a-input style="width: 250px" v-model="outbound.stream.ws.host"></a-input></a-form-item></td>
-        </tr>
-        <tr>
-            <td>{{ i18n "path" }}</td>
-            <td><a-form-item><a-input style="width: 250px;" v-model.trim="outbound.stream.ws.path"></a-input></a-form-item></td>
-        </tr>
+        <a-form-item label='{{ i18n "host" }}'>
+            <a-input v-model="outbound.stream.ws.host"></a-input>
+        </a-form-item>
+        <a-form-item label='{{ i18n "path" }}'>
+            <a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input>
+        </a-form-item>
     </template>
     
     <!-- http -->
     <template v-if="outbound.stream.network === 'http'">
-        <tr>
-            <td>{{ i18n "host" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.stream.http.host" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "path" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.stream.http.path" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "host" }}'>
+            <a-input v-model.trim="outbound.stream.http.host"></a-input>
+        </a-form-item>
+        <a-form-item label='{{ i18n "path" }}'>
+            <a-input v-model.trim="outbound.stream.http.path"></a-input>
+        </a-form-item>
     </template>
     
     <!-- quic -->
     <template v-if="outbound.stream.network === 'quic'">
-        <tr>
-            <td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="outbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option value="none">none</a-select-option>
-                        <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
-                        <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "password" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.stream.quic.key" style="width: 250px;"></a-input>
-                </a-form-item> 
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "camouflage" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="outbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option value="none">none (not camouflage)</a-select-option>
-                        <a-select-option value="srtp">srtp (video call)</a-select-option>
-                        <a-select-option value="utp">utp (BT download)</a-select-option>
-                        <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
-                        <a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
-                        <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
+            <a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option value="none">none</a-select-option>
+                <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
+                <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='{{ i18n "password" }}'>
+            <a-input v-model.trim="outbound.stream.quic.key"></a-input>
+        </a-form-item> 
+        <a-form-item label='{{ i18n "camouflage" }}'>
+            <a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option value="none">none (not camouflage)</a-select-option>
+                <a-select-option value="srtp">srtp (video call)</a-select-option>
+                <a-select-option value="utp">utp (BT download)</a-select-option>
+                <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
+                <a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
+                <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
+            </a-select>
+        </a-form-item>
     </template>
     
     <!-- grpc -->
     <template v-if="outbound.stream.network === 'grpc'">
-        <tr>
-            <td>serviceName</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="outbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>MultiMode</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='serviceName'>
+            <a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input>
+        </a-form-item>
+        <a-form-item label='MultiMode'>
+            <a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
+        </a-form-item>
     </template>
 </template>
 
 <!-- tls settings -->
 <template v-if="outbound.canEnableTls()">
-        <tr>
-            <td>{{ i18n "security" }}</td>
-            <td>
-                <a-form-item>
-                    <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-group>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "security" }}'>
+            <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-group>
+        </a-form-item>
         <template v-if="outbound.stream.isTls">
-            <tr>
-                <td>SNI</td>
-                <td>
-                    <a-form-item placeholder="Server Name Indication">
-                        <a-input v-model.trim="outbound.stream.tls.serverName" style="width: 250px"></a-input>
-                    </a-form-item>                
-                </td>
-            </tr>
-            <tr>
-                <td>uTLS</td>
-                <td>
-                    <a-form-item>
-                        <a-select v-model="outbound.stream.tls.fingerprint"
-                                    style="width: 250px" :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>
-                </td>
-            </tr>
-            <tr>
-                <td>ALPN</td>
-                <td>
-                    <a-form-item>
-                        <a-select
-                            mode="multiple"
-                            style="width: 250px"
-                            :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>
-                        </a-select>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>Allow insecure</td>
-                <td>
-                    <a-form-item>
-                        <a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
-                    </a-form-item>
-                </td>
-            </tr>
+            <a-form-item label="SNI" placeholder="Server Name Indication">
+                <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-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"
+                    v-model="outbound.stream.tls.alpn">
+                    <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
+                </a-select>
+            </a-form-item>
+            <a-form-item label="Allow insecure">
+                <a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
+            </a-form-item>
         </template>
         
         <!-- reality settings -->
         <template v-if="outbound.stream.isReality">
-                <tr>
-                    <td>{{ i18n "domainName" }}</td>
-                    <td>
-                        <a-form-item>
-                            <a-input v-model.trim="outbound.stream.reality.serverName" style="width: 250px"></a-input>
-                        </a-form-item>
-                    </td>
-                </tr>
-                <tr>
-                    <td>uTLS</td>
-                    <td>
-                        <a-form-item>
-                            <a-select v-model="outbound.stream.reality.fingerprint"
-                                      style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
-                                <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
-                            </a-select>
-                        </a-form-item>
-                    </td>
-                </tr>
-                <tr>
-                    <td>Short Id</td>
-                    <td>
-                        <a-form-item>
-                            <a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
-                        </a-form-item>
-                    </td>
-                </tr>
-                <tr>
-                    <td>SpiderX</td>
-                    <td>
-                        <a-form-item>
-                            <a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
-                        </a-form-item>
-                    </td>
-                </tr>
-                <tr>
-                    <td>Public Key</td>
-                    <td>
-                        <a-form-item>
-                            <a-input v-model.trim="outbound.stream.reality.publicKey" style="width: 250px"></a-input>
-                        </a-form-item>
-                    </td>
-                </tr>
+                <a-form-item label='{{ i18n "domainName" }}'>
+                    <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-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
+                    </a-select>
+                </a-form-item>
+                <a-form-item label="Short Id">
+                    <a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
+                </a-form-item>
+                <a-form-item label="SpiderX">
+                    <a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
+                </a-form-item>
+                <a-form-item label="Public Key">
+                            <a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
+                </a-form-item>
         </template>
 </template>
-    </table>
 </a-form>
 </a-tab-pane>
 <a-tab-pane key="2" tab="JSON" force-render="true">

+ 17 - 39
web/html/xui/form/protocol/dokodemo.html

@@ -1,42 +1,20 @@
 {{define "form/dokodemo"}}
-<a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "pages.inbounds.targetAddress"}}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.settings.address"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.destinationPort"}}</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="inbound.settings.port"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.network"}}</td>
-            <td>
-                <a-form-item>
-                    <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>           
-            </td>
-        </tr>
-        <tr>
-            <td>FollowRedirect</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="inbound.settings.followRedirect"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
+        <a-input v-model.trim="inbound.settings.address"></a-input>
+    </a-form-item>
+    <a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
+        <a-input-number v-model.number="inbound.settings.port"></a-input-number>
+    </a-form-item>
+    <a-form-item label='{{ i18n "pages.inbounds.network"}}'>
+        <a-select v-model="inbound.settings.network" :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='FollowRedirect'>
+        <a-switch v-model="inbound.settings.followRedirect"></a-switch>
+    </a-form-item>
 </a-form>
 {{end}}

+ 1 - 1
web/html/xui/form/protocol/http.html

@@ -1,5 +1,5 @@
 {{define "form/http"}}
-<a-form layout="inline">
+<a-form>
         <table style="width: 100%; text-align: center; margin-bottom: 10px;">
             <tr>
                 <td width="45%">{{ i18n "username" }}</td>

+ 36 - 38
web/html/xui/form/protocol/shadowsocks.html

@@ -1,6 +1,5 @@
 {{define "form/shadowsocks"}}
-<a-form layout="inline">
-    <template v-if="inbound.isSSMultiUser">
+<template v-if="inbound.isSSMultiUser">
     <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">  
         <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
             {{template "form/client"}}
@@ -20,40 +19,39 @@
             </table>
         </a-collapse-panel>
     </a-collapse>
-    </template>
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "encryption" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="inbound.settings.method" style="width: 250px;" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="inbound.isSS2022">
-            <td>{{ i18n "password" }}
-                <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.settings.password" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.network" }}</td>
-            <td>
-                <a-form-item>
-                    <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>
-            </td>
-        </tr>
-    </table>
-</a-form>
+</template>
+<table width="100%" class="ant-table-tbody">
+    <tr>
+        <td>{{ i18n "encryption" }}</td>
+        <td>
+            <a-form-item>
+                <a-select v-model="inbound.settings.method" style="width: 250px;" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
+                    <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
+                </a-select>
+            </a-form-item>
+        </td>
+    </tr>
+    <tr v-if="inbound.isSS2022">
+        <td>{{ i18n "password" }}
+            <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
+        </td>
+        <td>
+            <a-form-item>
+                <a-input v-model.trim="inbound.settings.password" style="width: 250px"></a-input>
+            </a-form-item>
+        </td>
+    </tr>
+    <tr>
+        <td>{{ i18n "pages.inbounds.network" }}</td>
+        <td>
+            <a-form-item>
+                <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>
+        </td>
+    </tr>
+</table>
 {{end}}

+ 30 - 49
web/html/xui/form/protocol/socks.html

@@ -1,52 +1,33 @@
 {{define "form/socks"}}
-<a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td style="width: 30%;">{{ i18n "password" }}</td>
-            <td>
-                <a-form-item>
-                    <a-switch :checked="inbound.settings.auth === 'password'"
-                              @change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="inbound.settings.auth === 'password'">
-            <td colspan="2">
-                <table style="width: 100%; text-align: center; margin-bottom: 10px;">
-                    <tr>
-                        <td width="45%">{{ i18n "username" }}</td>
-                        <td width="45%">{{ i18n "password" }}</td>
-                        <td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
-                    </tr>
-                </table>
-                <a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 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" }}'>
-                        <template slot="addonAfter">
-                            <a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
-                        </template>
-                    </a-input>
-                </a-input-group>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.enable" }} udp</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="inbound.settings.udp"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr v-if="inbound.settings.udp">
-            <td>IP</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.settings.ip"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+<a-form :colon="false" :label-col="{ md: {span:6} }" :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>
+    <a-form-item label="IP" v-if="inbound.settings.udp">
+        <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-form-item>
+    <template v-if="inbound.settings.auth === 'password'">
+        <table style="width: 100%; text-align: center; margin-bottom: 10px;">
+            <tr>
+                <td width="45%">{{ i18n "username" }}</td>
+                <td width="45%">{{ i18n "password" }}</td>
+                <td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
+            </tr>
+        </table>
+        <a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 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" }}'>
+                <template slot="addonAfter">
+                    <a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
+                </template>
+            </a-input>
+        </a-input-group>
+    </template>
 </a-form>
 {{end}}

+ 39 - 67
web/html/xui/form/protocol/trojan.html

@@ -1,30 +1,29 @@
 {{define "form/trojan"}}
-<a-form layout="inline" style="padding: 10px 0px;">
-    <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.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.trojans.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.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
-                    <td>[[ client.email ]]</td>
-                    <td>[[ client.password ]]</td>
-                </tr>
-            </table>
-        </a-collapse-panel>
-    </a-collapse>
-</a-form>
-<template v-if="inbound.isTcp">
+<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.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.trojans.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.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 && !inbound.stream.isReality">
     <a-form layout="inline">
         <a-form-item label="Fallbacks">
             <a-row>
-                <a-button type="primary" size="small" @click="inbound.settings.addFallback()">
+                <a-button type="primary" size="small"
+                        @click="inbound.settings.addFallback()">
                     +
                 </a-button>
             </a-row>
@@ -32,54 +31,27 @@
     </a-form>
 
     <!-- trojan fallbacks -->
-    <a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
+    <a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
         <a-divider style="margin:0;">
-            fallback[[ index + 1 ]]
+            Fallback[[ index + 1 ]]
             <a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
                     style="color: rgb(255, 77, 79);cursor: pointer;"/>
         </a-divider>
-        <table width="100%">
-            <tr>
-                <td style="width: 20%;">Name</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="fallback.name" style="width: 250px"></a-input>
-                    </a-form-item>            
-                </td>
-            </tr>
-            <tr>
-                <td>Alpn</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="fallback.alpn" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>Path</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="fallback.path" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>Dest</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="fallback.dest" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>xVer</td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-    </table>
+        <a-form-item label='Name'>
+            <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="fallback.xver" :min="0" :max="2"></a-input-number>
+        </a-form-item>
     </a-form>
     <a-divider style="margin:0;"></a-divider>
 </template>

+ 42 - 70
web/html/xui/form/protocol/vless.html

@@ -1,87 +1,59 @@
 {{define "form/vless"}}
-<a-form layout="inline" style="padding: 10px 0px;">
-    <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.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.vlesses.length">
-            <table width="100%">
-                <tr class="client-table-header">
-                    <th>{{ i18n "pages.inbounds.email" }}</th>
-                    <th>Flow</th>
-                    <th>ID</th>
-                </tr>
-                <tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
-                    <td>[[ client.email ]]</td>
-                    <td>[[ client.flow ]]</td>
-                    <td>[[ client.id ]]</td>
-                </tr>
-            </table>
-        </a-collapse-panel>
-    </a-collapse>
-</a-form>
-<template v-if="inbound.isTcp">
+<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.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.vlesses.length">
+        <table width="100%">
+            <tr class="client-table-header">
+                <th>{{ i18n "pages.inbounds.email" }}</th>
+                <th>Flow</th>
+                <th>ID</th>
+            </tr>
+            <tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
+                <td>[[ client.email ]]</td>
+                <td>[[ client.flow ]]</td>
+                <td>[[ client.id ]]</td>
+            </tr>
+        </table>
+    </a-collapse-panel>
+</a-collapse>
+<template v-if="inbound.isTcp && !inbound.stream.isReality">
     <a-form layout="inline">
         <a-form-item label="Fallbacks">
             <a-row>
-                <a-button type="primary" size="small" @click="inbound.settings.addFallback()">
+                <a-button type="primary" size="small"
+                        @click="inbound.settings.addFallback()">
                     +
                 </a-button>
             </a-row>
         </a-form-item>
     </a-form>
-    
+
     <!-- vless fallbacks -->
-    <a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
+    <a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
         <a-divider style="margin:0;">
-            fallback[[ index + 1 ]]
+            Fallback[[ index + 1 ]]
             <a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
                     style="color: rgb(255, 77, 79);cursor: pointer;"/>
         </a-divider>
-        <table width="100%">
-            <tr>
-                <td style="width: 20%;">Name</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="fallback.name" style="width: 250px"></a-input>
-                    </a-form-item>            
-                </td>
-            </tr>
-            <tr>
-                <td>Alpn</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="fallback.alpn" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>Path</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="fallback.path" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>Dest</td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model="fallback.dest" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>xVer</td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-    </table>
+        <a-form-item label='Name'>
+            <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="fallback.xver" :min="0" :max="2"></a-input-number>
+        </a-form-item>
     </a-form>
     <a-divider style="margin:0;"></a-divider>
 </template>

+ 19 - 21
web/html/xui/form/protocol/vmess.html

@@ -1,23 +1,21 @@
 {{define "form/vmess"}}
-<a-form layout="inline" style="padding: 10px 0px;">
-    <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>     
-    </a-collapse>
-    <a-collapse v-else>
-        <a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
-            <table width="100%">
-                    <tr class="client-table-header">
-                        <th>{{ i18n "pages.inbounds.email" }}</th>
-                        <th>ID</th>
-                </tr>
-                <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>
-                </tr>
-            </table>
-        </a-collapse-panel>
-    </a-collapse>
-</a-form>
+<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>
+</a-collapse>
+<a-collapse v-else>
+    <a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vmesses.length">
+        <table width="100%">
+            <tr class="client-table-header">
+                <th>{{ i18n "pages.inbounds.email" }}</th>
+                <th>ID</th>
+            </tr>
+            <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>
+            </tr>
+        </table>
+    </a-collapse-panel>
+</a-collapse>
 {{end}}

+ 19 - 18
web/html/xui/form/sniffing.html

@@ -1,21 +1,22 @@
 {{define "form/sniffing"}}
-<a-form layout="inline">
-    <a-form-item>
-        <span slot="label">
-            Sniffing
-            <a-tooltip>
-                <template slot="title">
-                    <span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
-                </template>
-                <a-icon type="question-circle" theme="filled"></a-icon>
-            </a-tooltip>
-        </span>
-        <a-switch v-model="inbound.sniffing.enabled"></a-switch>
-    </a-form-item>
-    <a-form-item>
-        <a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
-            <a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
-        </a-checkbox-group>
-    </a-form-item>
+<a-divider style="margin:0;"></a-divider>
+<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
+  <a-form-item>
+    <span slot="label">
+        Sniffing
+        <a-tooltip>
+            <template slot="title">
+                <span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
+            </template>
+            <a-icon type="question-circle"></a-icon>
+        </a-tooltip>
+    </span>
+    <a-switch v-model="inbound.sniffing.enabled"></a-switch>
+  </a-form-item>
+  <a-form-item :wrapper-col="{span:24}">
+    <a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
+      <a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
+    </a-checkbox-group>
+  </a-form-item>
 </a-form>
 {{end}}

+ 20 - 26
web/html/xui/form/stream/external_proxy.html

@@ -1,32 +1,26 @@
 {{define "form/externalProxy"}}
-<a-form layout="inline">
+<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
     <a-divider style="margin:0;"></a-divider>
     <a-form-item label="External Proxy">
-    <a-switch v-model="externalProxy"></a-switch>
-    <a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
+        <a-switch v-model="externalProxy"></a-switch>
+        <a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
     </a-form-item>
-    <table width="100%" class="ant-table-tbody" v-if="externalProxy" style="margin-bottom:5px">
-        <tr style="line-height: 40px;">
-            <td width="100%">
-                <a-input-group style="margin: 0 5px;" 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-option value="none">{{ i18n "none" }}</a-select-option>
-                                <a-select-option value="tls">TLS</a-select-option>
-                            </a-select>
-                        </a-tooltip>
-                    </template>
-                    <a-input style="width: 35%" 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="65531"></a-input-number>
-                    </a-tooltip>
-                    <a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
-                    <a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
-                </a-input-group>
-            </td>
-        </tr>
-    </table>
+    <a-input-group style="margin: 5px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
+        <template>
+            <a-tooltip title="Force TLS">
+                <a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
+                    <a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
+                    <a-select-option value="none">{{ i18n "none" }}</a-select-option>
+                    <a-select-option value="tls">TLS</a-select-option>
+                </a-select>
+            </a-tooltip>
+        </template>
+        <a-input style="width: 35%" 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="65531"></a-input-number>
+        </a-tooltip>
+        <a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
+        <a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
+    </a-input-group>
 </a-form>
 {{end}}

+ 7 - 19
web/html/xui/form/stream/stream_grpc.html

@@ -1,22 +1,10 @@
 {{define "form/streamGRPC"}}
-<a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>serviceName</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>MultiMode</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label="Service Name">
+        <a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
+    </a-form-item>
+    <a-form-item label="MultiMode">
+        <a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
+    </a-form-item>
 </a-form>
 {{end}}

+ 16 - 21
web/html/xui/form/stream/stream_http.html

@@ -1,24 +1,19 @@
 {{define "form/streamHTTP"}}
-<a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "path" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>host</td>
-            <td>
-                <a-form-item>
-                    <a-row v-for="(host, index) in inbound.stream.http.host">
-                        <a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
-                    </a-row>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label='{{ i18n "path" }}'>
+        <a-input v-model.trim="inbound.stream.http.path"></a-input>
+    </a-form-item>
+    <a-form-item>
+        <template slot="label">{{ i18n "host" }}
+            <a-button size="small" @click="inbound.stream.http.addHost()">+</a-button>
+        </template>
+        <template v-for="(host, index) in inbound.stream.http.host">
+            <a-input v-model.trim="inbound.stream.http.host[index]">
+                <a-button size="small" slot="addonAfter"
+                @click="inbound.stream.http.removeHost(index)"
+                v-if="inbound.stream.http.host.length>1">-</a-button>
+            </a-input>
+        </template>
+    </a-form-item>
 </a-form>
 {{end}}

+ 35 - 83
web/html/xui/form/stream/stream_kcp.html

@@ -1,86 +1,38 @@
 {{define "form/streamKCP"}}
-<a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "camouflage" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="inbound.stream.kcp.type" style="width: 250px;"
-                        :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option value="none">None (Not Camouflage)</a-select-option>
-                        <a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option>
-                        <a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option>
-                        <a-select-option value="wechat-video">Wechat-Video (Camouflage WeChat Video)</a-select-option>
-                        <a-select-option value="dtls">DTLS (Camouflage DTLS 1.2 Packages)</a-select-option>
-                        <a-select-option value="wireguard">Wireguard (Camouflage Wireguard Packages)</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "password" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>MTU</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>TTI (ms)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Uplink Capacity (MB/S)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Downlink Capacity (MB/S)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Congestion</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Read Buffer Size (MB)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Write Buffer Size (MB)</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label='{{ i18n "camouflage" }}'>
+        <a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
+            <a-select-option value="none">none (not camouflage)</a-select-option>
+            <a-select-option value="srtp">SRTP (video call)</a-select-option>
+            <a-select-option value="utp">UTP (BT DownloaD)</a-select-option>
+            <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
+            <a-select-option value="dtls">DTLS (DTLS 1.2 packages)</a-select-option>
+            <a-select-option value="wireguard">WireGuard (WireGuard packages)</a-select-option>
+        </a-select>
+    </a-form-item>
+    <a-form-item label='{{ i18n "password" }}'>
+        <a-input v-model="inbound.stream.kcp.seed"></a-input>
+    </a-form-item>
+    <a-form-item label='MTU'>
+        <a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
+    </a-form-item>
+    <a-form-item label='TTI(ms)'>
+        <a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
+    </a-form-item>
+    <a-form-item label='Uplink(Mb/s)'>
+        <a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
+    </a-form-item> 
+    <a-form-item label='Downlink(Mb/s)'>
+        <a-input-number v-model.number="inbound.stream.kcp.downCap"></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"></a-input-number>
+    </a-form-item>
+    <a-form-item label='Write buffer(MB)'>
+        <a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
+    </a-form-item>
 </a-form>
 {{end}}

+ 21 - 40
web/html/xui/form/stream/stream_quic.html

@@ -1,43 +1,24 @@
 {{define "form/streamQUIC"}}
-<a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="inbound.stream.quic.security" style="width: 250px;"
-                        :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option value="none">none</a-select-option>
-                        <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
-                        <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "password" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.stream.quic.key" style="width: 250px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "camouflage" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="inbound.stream.quic.type" style="width: 280px;"
-                        :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option value="none">none (not camouflage)</a-select-option>
-                        <a-select-option value="srtp">srtp (camouflage video call)</a-select-option>
-                        <a-select-option value="utp">utp (camouflage BT download)</a-select-option>
-                        <a-select-option value="wechat-video">wechat-video (camouflage WeChat video)</a-select-option>
-                        <a-select-option value="dtls">dtls (camouflage DTLS 1.2 packages)</a-select-option>
-                        <a-select-option value="wireguard">wireguard (camouflage wireguard packages)</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
+        <a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
+            <a-select-option value="none">none</a-select-option>
+            <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
+            <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
+        </a-select>
+    </a-form-item>
+    <a-form-item label='{{ i18n "password" }}'>
+        <a-input v-model.trim="inbound.stream.quic.key"></a-input>
+    </a-form-item> 
+    <a-form-item label='{{ i18n "camouflage" }}'>
+        <a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
+            <a-select-option value="none">none (not camouflage)</a-select-option>
+            <a-select-option value="srtp">SRTP (video call)</a-select-option>
+            <a-select-option value="utp">UTP (BT Download)</a-select-option>
+            <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
+            <a-select-option value="dtls">DTLS (DTLS 1.2 packages)</a-select-option>
+            <a-select-option value="wireguard">WireGuard (WireGuard Packages)</a-select-option>
+        </a-select>
+    </a-form-item>
 </a-form>
 {{end}}

+ 3 - 4
web/html/xui/form/stream/stream_settings.html

@@ -1,9 +1,9 @@
 {{define "form/streamSettings"}}
 <!-- select stream network -->
-<a-form layout="inline">
-    <a-form-item label='{{ i18n "transmission" }}'>
+<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label="{{ i18n "transmission" }}">
         <a-select v-model="inbound.stream.network" @change="streamNetworkChange"
-            :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100px;">
+            :dropdown-class-name="themeSwitcher.currentTheme">
             <a-select-option value="tcp">TCP</a-select-option>
             <a-select-option value="kcp">KCP</a-select-option>
             <a-select-option value="ws">WS</a-select-option>
@@ -43,7 +43,6 @@
 <template v-if="inbound.stream.network === 'grpc'">
     {{template "form/streamGRPC"}}
 </template>
-
 <!-- sockopt -->
 <template>
     {{template "form/streamSockopt"}}

+ 20 - 40
web/html/xui/form/stream/stream_sockopt.html

@@ -1,46 +1,26 @@
 {{define "form/streamSockopt"}}
-<a-form layout="inline">
+<a-divider style="margin:0;"></a-divider>
+<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
     <a-form-item label="Transparent Proxy">
         <a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
     </a-form-item>
-    <table width="100%" class="ant-table-tbody" v-if="inbound.stream.sockoptSwitch">
-        <tr>
-            <td>Accept Proxy Protocol</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>TCP FastOpen</td>
-            <td>
-                <a-form-item>
-                    <a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Route Mark</td>
-            <td>
-                <a-form-item>
-                    <a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>T-Proxy</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
-                        :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">T-Proxy</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+    <template v-if="inbound.stream.sockoptSwitch">
+        <a-form-item label="Accept Proxy Protocol">
+            <a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
+        </a-form-item>
+        <a-form-item label="TCP FastOpen">
+            <a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
+        </a-form-item>
+        <a-form-item label="Route Mark">
+            <a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
+        </a-form-item>
+        <a-form-item label="T-Proxy">
+            <a-select v-model="inbound.stream.sockopt.tproxy" :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">T-Proxy</a-select-option>
+            </a-select>
+        </a-form-item>
+    </template>
 </a-form>
 {{end}}

+ 61 - 98
web/html/xui/form/stream/stream_tcp.html

@@ -1,113 +1,76 @@
 {{define "form/streamTCP"}}
 <!-- tcp type -->
-<a-form layout="inline">
+<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
     <a-form-item label="Accept 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'">
+            :checked="inbound.stream.tcp.type === 'http'"
+            @change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
         </a-switch>
     </a-form-item>
 </a-form>
 
 <!-- tcp request -->
-<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td style="vertical-align: top; padding-top: 10px;">{{ i18n "pages.inbounds.stream.tcp.requestPath" }}
-                <a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-row v-for="(path, index) in inbound.stream.tcp.request.path">
-                        <a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;">
-                            <a-button size="small" slot="addonAfter"
-                            @click="inbound.stream.tcp.request.removePath(index)"
-                            v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
-                        </a-input>
-                    </a-row>
-                </a-form-item> 
-            </td>
-        </tr>
-        <tr>
-            <td colspan="2" width="100%">
-                <a-form-item>
-                    <span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
-                    <a-button size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
-                    <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 slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
-                        </a-input>
-                    </a-input-group>
-                </a-form-item>
-            </td>
-        </tr>
+<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
+        <a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
+    </a-form-item>
+    <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}'>
+                    <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.requestPath" }}
+            <a-button 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 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.general.requestHeader" }}'>
+        <a-button size="small" @click="inbound.stream.tcp.request.addHeader('', '')">+</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>
+            <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.tcp.request.removeHeader(index)">-</a-button>
+            </a-input>
+        </a-input-group>
+    </a-form-item>
         <!-- tcp response -->
-        <tr>
-            <td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td colspan="2" width="100%">
-                <a-form-item>
-                    <span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
-                    <a-button size="small" style="margin-left: 10px"
-                        @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
-                    <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" }}'>
-                            <template slot="addonAfter">
-                                <a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
-                            </template>
-                        </a-input>
-                    </a-input-group>
-                </a-form-item>
-            </td>
-        </tr>
-    </table>
+    <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
+        <a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
+    </a-form-item>
+    <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}'>
+        <a-input v-model.trim="inbound.stream.tcp.response.status"></a-input>
+    </a-form-item>
+    <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
+        <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 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>
+            <a-input style="width: 50%" v-model.trim="header.value"
+                        placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
+                <template slot="addonAfter">
+                    <a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
+                </template>
+            </a-input>
+        </a-input-group>
+    </a-form-item>
 </a-form>
 {{end}}

+ 11 - 18
web/html/xui/form/stream/stream_ws.html

@@ -1,28 +1,21 @@
 {{define "form/streamWS"}}
-<a-form layout="inline">
+<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
     <a-form-item label="AcceptProxyProtocol">
         <a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
     </a-form-item>
-    <br>
     <a-form-item label='{{ i18n "path" }}'>
         <a-input v-model.trim="inbound.stream.ws.path"></a-input>
     </a-form-item>
-    <br>
-    <a-form-item>
-        <a-row>
-            <span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
-            <a-button type="primary" size="small" style="margin-left: 10px"
-                @click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
-        </a-row>
-        <a-input-group v-for="(header, index) in inbound.stream.ws.headers">
-            <a-input style="width: 50%" v-model.trim="header.name"
-                addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
-            <a-input style="width: 50%" v-model.trim="header.value"
-                addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
-                <template slot="addonAfter">
-                    <a-button type="primary" size="small" style="margin-left: 10px"
-                        @click="inbound.stream.ws.removeHeader(index)">-</a-button>
-                </template>
+    <a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
+        <a-button 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>
+            <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.ws.removeHeader(index)">-</a-button>
             </a-input>
         </a-input-group>
     </a-form-item>

+ 182 - 382
web/html/xui/form/tls_settings.html

@@ -1,394 +1,194 @@
 {{define "form/tlsSettings"}}
 <!-- tls enable -->
-
-<a-form v-if="inbound.canEnableTls()" layout="inline">
+<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
     <a-divider style="margin:0;"></a-divider>
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td colspan="2">
-                <a-form-item label='{{ i18n "security" }}'>
-                    <a-radio-group v-model="inbound.stream.security" button-style="solid">
-                        <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
-                        <a-tooltip>
-                            <template slot="title">
-                                <span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
-                            </template>
-                            <a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button>
-                        </a-tooltip>
-                        <a-tooltip>
-                            <template slot="title">
-                                <span>{{ i18n "pages.inbounds.realityDesc" }}</span>
-                            </template>
-                            <a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
-                        </a-tooltip>
-                        <a-radio-button value="tls">TLS</a-radio-button>
-                    </a-radio-group>
-                </a-form-item>
-            </td>
-        </tr>
-
-        <!-- tls settings -->
-        <template v-if="inbound.stream.isTls">
-            <tr>
-                <td>SNI</td>
-                <td>
-                    <a-form-item placeholder="Server Name Indication">
-                        <a-input v-model.trim="inbound.stream.tls.sni" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>CipherSuites</td>
-                <td>
-                    <a-form-item>
-                        <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px"
-                            :dropdown-class-name="themeSwitcher.currentTheme">
-                            <a-select-option value="">auto</a-select-option>
-                            <a-select-option v-for="key,value in TLS_CIPHER_OPTION" 
-                            :value="key">[[ value]]</a-select-option>
-                        </a-select>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>Min/Max Version</td>
-                <td>
-                    <a-form-item>
-                        <a-input-group compact>
-                            <a-select style="width: 125px" v-model="inbound.stream.tls.minVersion"
-                                :dropdown-class-name="themeSwitcher.currentTheme">
-                                <a-select-option v-for="key in TLS_VERSION_OPTION" 
-                                :value="key">[[ key ]]</a-select-option>
-                            </a-select>
-                            <a-select style="width: 125px" v-model="inbound.stream.tls.maxVersion"
-                                :dropdown-class-name="themeSwitcher.currentTheme">
-                                <a-select-option v-for="key in TLS_VERSION_OPTION" 
-                                :value="key">[[ key ]]</a-select-option>
-                            </a-select>
-                        </a-input-group>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>uTLS</td>
-                <td>
-                    <a-form-item>
-                        <a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px"
-                            :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>
-                </td>
-            </tr>
-            <tr>
-                <td>ALPN</td>
-                <td>
-                    <a-form-item>
-                        <a-select mode="multiple" style="width: 250px" 
-                            :dropdown-class-name="themeSwitcher.currentTheme"
-                            v-model="inbound.stream.tls.alpn">
-                            <a-select-option v-for="alpn in ALPN_OPTION" 
-                            :value="alpn">[[ alpn ]]</a-select-option>
-                        </a-select>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>Allow insecure</td>
-                <td>
-                    <a-form-item>
-                        <a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>Reject Unknown SNI</td>
-                <td>
-                    <a-form-item>
-                        <a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
-                    </a-form-item>
-                </td>
-            </tr>
-            <template v-for="cert,index in inbound.stream.tls.certs">
-                <tr>
-                    <td>{{ i18n "certificate" }}</td>
-                    <td>
-                        <a-form-item>
-                            <a-radio-group v-model="cert.useFile" button-style="solid">
-                                <a-radio-button 
-                                    :value="true">{{ i18n "pages.inbounds.certificatePath"}}
-                                </a-radio-button>
-                                <a-radio-button 
-                                    :value="false">{{ i18n "pages.inbounds.certificateContent"}}
-                                </a-radio-button>
-                            </a-radio-group>
-                            <a-button v-if="index === 0" type="primary" size="small"
-                                @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
-                            <a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
-                                @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
-                        </a-form-item>
-                    </td>
-                </tr>
-                <template v-if="cert.useFile">
-                    <tr>
-                        <td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
-                        <td>
-                            <a-form-item>
-                                <a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
-                            </a-form-item>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>{{ i18n "pages.inbounds.keyPath" }}</td>
-                        <td>
-                            <a-form-item>
-                                <a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
-                            </a-form-item>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td></td>
-                        <td>
-                            <a-button type="primary" icon="import" @click="setDefaultCertData(index)">
-                                {{ i18n "pages.inbounds.setDefaultCert" }}
-                            </a-button>
-                        </td>
-                    </tr>
+    <a-form-item label='{{ i18n "security" }}'>
+        <a-radio-group v-model="inbound.stream.security" button-style="solid">
+            <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
+            <a-tooltip>
+                <template slot="title">
+                    <span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
                 </template>
-                <template v-else>
-                    <tr>
-                        <td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
-                        <td>
-                            <a-form-item>
-                                <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
-                            </a-form-item>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>{{ i18n "pages.inbounds.keyContent" }}</td>
-                        <td>
-                            <a-form-item>
-                                <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
-                            </a-form-item>
-                        </td>
-                    </tr>
+                <a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button>
+            </a-tooltip>
+            <a-tooltip>
+                <template slot="title">
+                    <span>{{ i18n "pages.inbounds.realityDesc" }}</span>
                 </template>
-                <tr>
-                    <td>ocspStapling</td>
-                    <td>
-                        <a-form-item>
-                            <a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
-                        </a-form-item>
-                    </td>
-                </tr>
-            </template>
-        </template>
+                <a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
+            </a-tooltip>
+            <a-radio-button value="tls">TLS</a-radio-button>
+        </a-radio-group>
+    </a-form-item>
 
-        <!-- xtls settings -->
-        <template v-else-if="inbound.xtls">
-            <tr>
-                <td>
-                    <span>SNI</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model.trim="inbound.stream.xtls.server" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>Alpn</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-select mode="multiple" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme"
-                            v-model="inbound.stream.xtls.alpn">
-                            <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
-                        </a-select>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>Allow insecure</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
-                    </a-form-item>
-                </td>
-            </tr>
-            <template v-for="cert,index in inbound.stream.xtls.certs">
-                <tr>
-                    <td>{{ i18n "certificate" }}</td>
-                    <td>
-                        <a-form-item>
-                            <a-radio-group v-model="cert.useFile" button-style="solid">
-                                <a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath"}}</a-radio-button>
-                                <a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent"}}</a-radio-button>
-                            </a-radio-group>
-                            <a-button v-if="index === 0" type="primary" size="small"
-                                @click="inbound.stream.xtls.addCert()" style="margin-left: 10px">+</a-button>
-                            <a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
-                                @click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
-                        </a-form-item>
-                    </td>
-                </tr>
-                <template v-if="cert.useFile">
-                    <tr>
-                        <td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
-                        <td>
-                            <a-form-item>
-                                <a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
-                            </a-form-item>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>{{ i18n "pages.inbounds.keyPath" }}</td>
-                        <td>
-                            <a-form-item>
-                                <a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
-                            </a-form-item>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td></td>
-                        <td>
-                            <a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">
-                                {{ i18n "pages.inbounds.setDefaultCert" }}
-                            </a-button>
-                        </td>
-                    </tr>
-                </template>
-                <template v-else>
-                    <tr>
-                        <td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
-                        <td>
-                            <a-form-item>
-                                <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
-                            </a-form-item>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>{{ i18n "pages.inbounds.keyContent" }}</td>
-                        <td>
-                            <a-form-item>
-                                <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
-                            </a-form-item>
-                        </td>
-                    </tr>
-                </template>
+    <!-- tls settings -->
+    <template v-if="inbound.stream.isTls">
+        <a-form-item label="SNI" placeholder="Server Name Indication">
+            <a-input v-model.trim="inbound.stream.tls.sni"></a-input>
+        </a-form-item>
+        <a-form-item label="CipherSuites">
+            <a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option value="">auto</a-select-option>
+                <a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label="Min/Max Version">
+            <a-input-group compact>
+                <a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
+                    <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
+                </a-select>
+                <a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
+                    <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
+                </a-select>
+            </a-input-group>
+        </a-form-item>
+        <a-form-item label="uTLS">
+            <a-select v-model="inbound.stream.tls.settings.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"
+                v-model="inbound.stream.tls.alpn">
+                <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label="Allow insecure">
+            <a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
+        </a-form-item>
+        <a-form-item label="Reject Unknown SNI">
+            <a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
+        </a-form-item>
+        <template v-for="cert,index in inbound.stream.tls.certs">
+            <a-form-item label='{{ i18n "certificate" }}'>
+                <a-radio-group v-model="cert.useFile" button-style="solid">
+                    <a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
+                    <a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
+                </a-radio-group>
+                <a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"
+                    style="margin-left: 10px">+</a-button>
+                <a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
+                    @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
+            </a-form-item>
+            <template v-if="cert.useFile">
+                <a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
+                    <a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
+                </a-form-item>
+                <a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
+                    <a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
+                </a-form-item>
+                <a-form-item label=" ">
+                    <a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n
+                        "pages.inbounds.setDefaultCert" }}</a-button>
+                </a-form-item>
+            </template>
+            <template v-else>
+                <a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
+                    <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
+                </a-form-item>
+                <a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
+                    <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
+                </a-form-item>
             </template>
+            <a-form-item label='ocspStapling'>
+                <a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
+            </a-form-item>
         </template>
+    </template>
 
-        <!-- reality settings -->
-        <template v-else-if="inbound.reality">
-            <tr>
-                <td>
-                    <span>Show</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-switch v-model="inbound.stream.reality.show"></a-switch>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>xVer</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input-number v-model="inbound.stream.reality.xver" :min="0"
-                            style="width: 60px"></a-input-number>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>uTLS</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 135px"
-                            :dropdown-class-name="themeSwitcher.currentTheme">
-                            <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
-                        </a-select>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>Dest</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model.trim="inbound.stream.reality.dest" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>Server Names</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>ShortIds</span> <a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()"
-                        type="sync"> </a-icon>
-                </td>
-                <td>
-                    <a-form-item>
-
-                        <a-input v-model.trim="inbound.stream.reality.shortIds" style="width: 150px;"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>SpiderX</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width: 150px;"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>Private Key</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <span>Public Key</span>
-                </td>
-                <td>
-                    <a-form-item>
-                        <a-input v-model.trim="inbound.stream.reality.settings.publicKey"
-                            style="width: 250px"></a-input>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td colspan="2">
-                    <a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
-                </td>
-            </tr>
+    <!-- xtls settings -->
+    <template v-else-if="inbound.xtls">
+        <a-form-item label="SNI" placeholder="Server Name Indication">
+            <a-input v-model.trim="inbound.stream.xtls.sni"></a-input>
+        </a-form-item>
+        <a-form-item label="ALPN">
+            <a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
+                v-model="inbound.stream.xtls.alpn">
+                <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label="Allow insecure">
+            <a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
+        </a-form-item>
+        <template v-for="cert,index in inbound.stream.xtls.certs">
+            <a-form-item label='{{ i18n "certificate" }}'>
+                <a-radio-group v-model="cert.useFile" button-style="solid">
+                    <a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
+                    <a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
+                </a-radio-group>
+                <a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()"
+                    style="margin-left: 10px">+</a-button>
+                <a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
+                    @click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
+            </a-form-item>
+            <template v-if="cert.useFile">
+                <a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
+                    <a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
+                </a-form-item>
+                <a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
+                    <a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
+                </a-form-item>
+                <a-form-item label=" ">
+                    <a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n
+                        "pages.inbounds.setDefaultCert" }}</a-button>
+                </a-form-item>
+            </template>
+            <template v-else>
+                <a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
+                    <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
+                </a-form-item>
+                <a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
+                    <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
+                </a-form-item>
+            </template>
         </template>
-    </table>
+    </template>
+    <!-- reality settings -->
+    <template v-if="inbound.stream.isReality">
+        <a-form-item label='Show'>
+            <a-switch v-model="inbound.stream.reality.show"></a-switch>
+        </a-form-item>
+        <a-form-item label='Xver'>
+            <a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
+        </a-form-item>
+        <a-form-item label='uTLS'>
+            <a-select v-model="inbound.stream.reality.settings.fingerprint"
+                :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='Dest'>
+            <a-input v-model.trim="inbound.stream.reality.dest"></a-input>
+        </a-form-item>
+        <a-form-item label='Server Names'>
+            <a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
+        </a-form-item>
+        <a-form-item>
+            <template slot="label">
+                <a-tooltip>
+                    <template slot="title">
+                        <span>{{ i18n "pages.client.renew" }}</span>
+                    </template>
+                    Short Ids
+                    <a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync">
+                    </a-icon>
+            </template>
+            <a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
+        </a-form-item>
+        <a-form-item label='SpiderX'>
+            <a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
+        </a-form-item>
+        <a-form-item label='Private Key'>
+            <a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
+        </a-form-item>
+        <a-form-item label='Public Key'>
+            <a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
+        </a-form-item>
+        <a-form-item label=" ">
+            <a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
+        </a-form-item>
+    </template>
 </a-form>
 {{end}}

+ 158 - 133
web/html/xui/inbound_info_modal.html

@@ -3,72 +3,89 @@
     v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
     :closable="true"
     :mask-closable="true"
-    :class="themeSwitcher.currentTheme"
     :footer="null"
     width="600px"
+    :class="themeSwitcher.currentTheme"
     >
-    <table style="margin-bottom: 10px; width: 100%;">
-        <tr><td>
-                <table>
-                    <tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
-                    <tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag class="tag-of-wrap">[[ dbInbound.address ]]</a-tag></td></tr>
-                    <tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
-                </table>
-            </td>
-            <td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
-                <table>
-                    <tr>
-                        <td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
-                    </tr>
-                    <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
-                        <tr>
-                            <td>{{ i18n "host" }}</td>
-                            <td v-if="inbound.host"><a-tag>[[ inbound.host ]]</a-tag></td>
-                            <td v-else><a-tag class="tag-of-wrap" color="orange">{{ i18n "none" }}</a-tag></td></tr>
-                        </tr>
-                        <tr>
-                            <td>{{ i18n "path" }}</td>
-                            <td v-if="inbound.path"><a-tag class="tag-of-wrap">[[ inbound.path ]]</a-tag></td>
-                            <td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
-                        </tr>
-                    </template>
-
-                    <template v-if="inbound.isQuic">
-                        <tr><td>quic {{ i18n "encryption" }}</td><td><a-tag class="tag-of-wrap">[[ inbound.quicSecurity ]]</a-tag></td></tr>
-                        <tr><td>quic {{ i18n "password" }}</td><td><a-tag class="tag-of-wrap">[[ inbound.quicKey ]]</a-tag></td></tr>
-                        <tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag class="tag-of-wrap">[[ inbound.quicType ]]</a-tag></td></tr>
-                    </template>
-        
-                    <template v-if="inbound.isKcp">
-                        <tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag class="tag-of-wrap">[[ inbound.kcpType ]]</a-tag></td></tr>
-                        <tr><td>kcp {{ i18n "password" }}</td><td><a-tag class="tag-of-wrap">[[ inbound.kcpSeed ]]</a-tag></td></tr>
-                    </template>
-        
-                    <template v-if="inbound.isGrpc">
-                        <tr><td>grpc serviceName</td><td><a-tag class="tag-of-wrap">[[ inbound.serviceName ]]</a-tag></td></tr>
-                        <tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
-                    </template>
-                </table>
-            </td></tr>
-        <tr colspan="2" v-if="dbInbound.hasLink()">
-            <td>
-                {{ i18n "security" }}
-                <a-tag :color="inbound.stream.security == 'none' ? 'red' : 'blue'">[[ inbound.stream.security ]]</a-tag>
-                <br />
-                <template v-if="inbound.stream.security != 'none'">
-                {{ i18n "domainName" }}
-                <a-tag :color="inbound.serverName ? 'blue' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
-                </template>
-            </td>
-        </tr>
-    </table>
+    <a-row>
+        <a-col :xs="24" :md="12">
+            <table>
+                <tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
+                <tr><td>{{ i18n "pages.inbounds.address" }}</td><td>
+                    <a-tooltip :title="[[ dbInbound.address ]]">
+                        <a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag>
+                    </a-tooltip>
+                </td></tr>
+                <tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
+            </table>
+        </a-col>
+        <a-col :xs="24" :md="12">
+        <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
+            <table>
+                <tr>
+                    <td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
+                </tr>
+            <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
+                <tr>
+                    <td>{{ i18n "host" }}</td>
+                    <td v-if="inbound.host">
+                        <a-tooltip :title="[[ inbound.host ]]">
+                            <a-tag class="info-large-tag">[[ inbound.host ]]</a-tag>
+                        </a-tooltip>
+                    </td>
+                    <td v-else><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>
+                    <td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
+                </tr>
+            </template>
+            
+            <template v-if="inbound.isQuic">
+                <tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
+                <tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
+                <tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
+            </template>
+            
+            <template v-if="inbound.isKcp">
+                <tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
+                <tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
+            </template>
+            
+            <template v-if="inbound.isGrpc">
+                <tr><td>grpc serviceName</td><td>
+                    <a-tooltip :title="[[ inbound.serviceName ]]">
+                        <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>
+        </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 />
+            <template v-if="inbound.stream.security != 'none'">
+            {{ i18n "domainName" }}
+            <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
+            </template>
+        </template>
     <table v-if="dbInbound.isSS" style="margin-bottom: 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-tag class="tag-of-wrap-l">[[ inbound.settings.password ]]</a-tag></td>
+            <td>
+                <a-tooltip :title="[[ inbound.settings.password  ]]">
+                    <a-tag class="info-large-tag">[[ inbound.settings.password  ]]</a-tag>
+                </a-tooltip>
+            </td>
         </tr><tr>
             <td>{{ i18n "pages.inbounds.network" }}</td>
             <td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
@@ -83,15 +100,23 @@
             </tr>
             <tr v-if="infoModal.clientSettings.id">
                 <td>ID</td>
-                <td><a-tag class="tag-of-wrap-l">[[ infoModal.clientSettings.id ]]</a-tag></td>
+                <td><a-tag>[[ infoModal.clientSettings.id ]]</a-tag></td>
             </tr>
             <tr v-if="infoModal.inbound.canEnableTlsFlow()">
                 <td>Flow</td>
                 <td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
             </tr>
+            <tr v-if="infoModal.inbound.xtls">
+                <td>Flow</td>
+                <td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
+            </tr>
             <tr v-if="infoModal.clientSettings.password">
-                <td>Password</td>
-                <td><a-tag class="tag-of-wrap-l">[[ infoModal.clientSettings.password ]]</a-tag></td>
+                <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>
@@ -115,69 +140,69 @@
                 <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)">
-                    [[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
-                </a-tag>
-            </td>
-            <td>
-                <a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
-                    [[ sizeFormat(infoModal.clientSettings.totalGB) ]]
-                </a-tag>
-                <a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag>
-            </td>
-            <td>
-                <template v-if="infoModal.clientSettings.expiryTime > 0">
-                    <a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
-                        [[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
+            <tr>
+                <td>
+                    <a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
+                        [[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
                     </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">&infin;</a-tag>
-            </td>
-        </tr>
-    </table>
-    <template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
-        <a-divider>Subscription link</a-divider>
-        <a-row>
-            <a-col :span="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
-            <a-col :span="2">
-                <a-tooltip title='{{ i18n "copy" }}'>
-                    <button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
-                        <a-icon type="snippets"></a-icon>
-                    </button>
-                </a-tooltip>
-            </a-col>
-        </a-row>
-    </template>
-    <template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
-        <a-divider>Telegram Username</a-divider>
-        <a-row>
-            <a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
-            <a-col :span="2">
-                <a-tooltip title='{{ i18n "copy" }}'>
-                    <button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
-                        <a-icon type="snippets"></a-icon>
-                    </button>
-                </a-tooltip>
-            </a-col>
-        </a-row>
-    </template>
-    <template v-if="dbInbound.hasLink()">
-        <a-divider>URL</a-divider>
-        <a-row v-for="(link,index) in infoModal.links">
-            <a-col :span="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
-            <a-col :span="2" style="text-align: right;">
-                <a-tooltip title='{{ i18n "copy" }}'>
-                    <button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
-                        <a-icon type="snippets"></a-icon>
-                    </button>
-                </a-tooltip>
-            </a-col>
-        </a-row>
+                </td>
+                <td>
+                    <a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
+                        [[ sizeFormat(infoModal.clientSettings.totalGB) ]]
+                    </a-tag>
+                    <a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag>
+                </td>
+                <td>
+                    <template v-if="infoModal.clientSettings.expiryTime > 0">
+                        <a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
+                            [[ DateUtil.formatMillis(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">&infin;</a-tag>
+                </td>
+            </tr>
+        </table>
+        <template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
+            <a-divider>Subscription link</a-divider>
+            <a-row>
+                <a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
+                <a-col :sx="24" :md="2" style="text-align: right;">
+                    <a-tooltip title='{{ i18n "copy" }}'>
+                        <button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
+                            <a-icon type="snippets"></a-icon>
+                        </button>
+                    </a-tooltip>
+                </a-col>
+            </a-row>
+        </template>
+        <template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
+            <a-divider>Telegram ID</a-divider>
+            <a-row>
+                <a-col :sx="24" :md="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
+                <a-col :sx="24" :md="2" style="text-align: right;">
+                    <a-tooltip title='{{ i18n "copy" }}'>
+                        <button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
+                            <a-icon type="snippets"></a-icon>
+                        </button>
+                    </a-tooltip>
+                </a-col>
+            </a-row>
+        </template>
+        <template v-if="dbInbound.hasLink()">
+            <a-divider>URL</a-divider>
+            <a-row v-for="(link,index) in infoModal.links">
+                <a-col :sx="24" :md="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
+                <a-col :sx="24" :md="2" style="text-align: right;">
+                    <a-tooltip title='{{ i18n "copy" }}'>
+                        <button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
+                            <a-icon type="snippets"></a-icon>
+                        </button>
+                    </a-tooltip>
+                </a-col>
+            </a-row>
+        </template>
     </template>
-</template>
     <template v-else>
         <template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
             <a-divider>URL</a-divider>
@@ -217,7 +242,7 @@
                 <td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
             </tr>
             <template v-if="inbound.settings.auth == 'password'">
-                <tr>
+            <tr>
                 <td> </td>
                 <td>{{ i18n "username" }}</td>
                 <td>{{ i18n "password" }}</td>
@@ -226,9 +251,9 @@
                 <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" style="margin-bottom: 10px; width: 100%;">
+            </template>
+        </table>
+        <table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
             <tr>
                 <th> </th>
                 <th>{{ i18n "username" }}</th>
@@ -246,7 +271,6 @@
         visible: false,
         inbound: new Inbound(),
         dbInbound: new DBInbound(),
-        settings: null,
         clientSettings: null,
         clientStats: [],
         upStats: 0,
@@ -295,24 +319,24 @@
                 return this.infoModal.inbound;
             },
             get isActive() {
-                if (infoModal.clientStats) {
+                if(infoModal.clientStats){
                     return infoModal.clientStats.enable;
                 }
                 return true;
             },
             get isEnable() {
-                if (infoModal.clientSettings) {
+                if(infoModal.clientSettings){
                     return infoModal.clientSettings.enable;
                 }
                 return infoModal.dbInbound.isEnable;
             },
         },
         methods: {
-            copyToClipboard(elmentId, content) {
+            copyToClipboard(elmentId,content) {
                 this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
-                    text: () => content,
-                });
-                this.infoModal.clipboard.on('success', () => {
+                        text: () => content,
+                    });
+                this.infoModal.clipboard.on('success', () => { 
                     app.$message.success('{{ i18n "copied" }}')
                     this.infoModal.clipboard.destroy();
                 });
@@ -321,7 +345,8 @@
                 return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
             }
         },
+        
     });
 
 </script>
-{{end}}
+{{end}}

+ 5 - 0
web/html/xui/inbound_modal.html

@@ -98,6 +98,11 @@
                         client.flow = "";
                     });
                 }
+                if ((this.inModal.inbound.protocol == Protocols.VLESS || this.inModal.inbound.protocol == Protocols.TROJAN) && !inModal.inbound.xtls) {
+                    this.inModal.inbound.settings.vlesses.forEach(client => {
+                        client.flow = "";
+                    });
+                }
             },
             SSMethodChange() {
                 if (this.inModal.inbound.isSSMultiUser) {

+ 4 - 0
web/html/xui/inbounds.html

@@ -43,6 +43,10 @@
         0%, 50%, 100%   { transform: scale(1);  opacity:  1; }
         10%             { transform: scale(1.5); opacity: .2; }
     }
+    .info-large-tag {
+        max-width: 200px;
+        overflow: hidden;
+    }
 </style>
 
 <body>

+ 4 - 1
web/html/xui/xray.html

@@ -3,7 +3,6 @@
 {{template "head" .}}
 <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
 <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
-<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css">
 <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
 
 <script src="{{ .base_path }}assets/js/model/outbound.js"></script>
@@ -29,6 +28,10 @@
             margin: 0;
             padding: 12px .5rem;
         }
+        .ant-table-thead > tr > th,
+        .ant-table-tbody > tr > td {
+            padding: 10px 0px;
+        }
     }
 
     .ant-tabs-bar {

+ 33 - 69
web/html/xui/xray_reverse_modal.html

@@ -2,77 +2,41 @@
 <a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
          :confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
          :ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
-    <a-form layout="inline">
-    <table width="100%" class="ant-table-tbody">
-        <tr>
-            <td style="width: 30%;" >{{ i18n "pages.xray.outbound.type" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="reverseModal.reverse.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.xray.outbound.tag" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="reverseModal.reverse.tag" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.xray.outbound.domain" }}</td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="reverseModal.reverse.domain" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
+    <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+        <a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
+            <a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.xray.outbound.tag" }}'>
+            <a-input v-model.trim="reverseModal.reverse.tag"></a-input>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.xray.outbound.domain" }}'>
+            <a-input v-model.trim="reverseModal.reverse.domain"></a-input>
+        </a-form-item>
         <template v-if="reverseModal.reverse.type=='bridge'">
-        <tr>
-            <td>{{ i18n "pages.xray.outbound.intercon" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="reverseModal.rules[0].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>{{ i18n "pages.xray.rules.outbound" }}</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="reverseModal.rules[1].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
+        <a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
+            <a-select v-model="reverseModal.rules[0].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='{{ i18n "pages.xray.rules.outbound" }}'>
+            <a-select v-model="reverseModal.rules[1].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
+            </a-select>
+        </a-form-item>
         </template>
         <template v-else>
-            <tr>
-                <td>{{ i18n "pages.xray.outbound.intercon" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-checkbox-group
-                            v-model="reverseModal.rules[0].inboundTag"
-                            :options="reverseModal.inboundTags"></a-checkbox-group>
-                    </a-form-item>
-                </td>
-            </tr>
-            <tr>
-                <td>{{ i18n "pages.xray.rules.inbound" }}</td>
-                <td>
-                    <a-form-item>
-                        <a-checkbox-group
-                            v-model="reverseModal.rules[1].inboundTag"
-                            :options="reverseModal.inboundTags"></a-checkbox-group>
-                    </a-form-item>
-                </td>
-            </tr>
+            <a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
+                <a-checkbox-group
+                    v-model="reverseModal.rules[0].inboundTag"
+                    :options="reverseModal.inboundTags"></a-checkbox-group>
+            </a-form-item>
+            <a-form-item label='{{ i18n "pages.xray.rules.inbound" }}'>
+                <a-checkbox-group
+                    v-model="reverseModal.rules[1].inboundTag"
+                    :options="reverseModal.inboundTags"></a-checkbox-group>
+            </a-form-item>
         </template>
     </table>
     </a-form>
@@ -173,4 +137,4 @@
     });
 
 </script>
-{{end}}
+{{end}}

+ 68 - 117
web/html/xui/xray_rule_modal.html

@@ -2,149 +2,100 @@
 <a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
          :confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
          :ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
-    <a-form layout="inline">
-        <table width="100%" class="ant-table-tbody">
-            <tr>
-            <td style="width: 30%;">Domain Matcher</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="ruleModal.rule.domainMatcher" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
+    <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
+        <a-form-item label='Domain Matcher'>
+                    <a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
                         <a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
                     </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Source IPs
+        </a-form-item>
+        <a-form-item>
+            <template slot="label">
                 <a-tooltip>
                     <template slot="title">
                         <span>{{ i18n "pages.xray.rules.useComma" }}</span>
                     </template>
-                    <a-icon type="question-circle"></a-icon>
+                    Source IPs <a-icon type="question-circle"></a-icon>
                 </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="ruleModal.rule.source" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Source Port
+            </template>
+            <a-input v-model.trim="ruleModal.rule.source"></a-input>
+        </a-form-item>
+        <a-form-item>
+            <template slot="label">Source Port
                 <a-tooltip>
                     <template slot="title">
                         <span>{{ i18n "pages.xray.rules.useComma" }}</span>
                     </template>
-                    <a-icon type="question-circle"></a-icon>
+                    Source Port <a-icon type="question-circle"></a-icon>
                 </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="ruleModal.rule.sourcePort" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Network</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="ruleModal.rule.network" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="x in ['','tcp','tdp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Protocol</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="ruleModal.rule.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td colspan="2">
-                <a-form-item>
-                    <span>Attributes</span>
-                    <a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
-                    <a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
-                        <a-input style="width: 50%" v-model="attr[0]" 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="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
-                            <a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
-                        </a-input>
-                    </a-input-group>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>IP
+            </template>
+            <a-input v-model.trim="ruleModal.rule.sourcePort"></a-input>
+        </a-form-item>
+        <a-form-item label='Network'>
+            <a-select v-model="ruleModal.rule.network" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="x in ['','tcp','tdp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='Protocol'>
+            <a-select v-model="ruleModal.rule.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='Attributes'>
+            <a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
+        </a-form-item>
+        <a-form-item :wrapper-col="{span: 24}">
+            <a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
+                <a-input style="width: 50%" v-model="attr[0]" 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="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
+                    <a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
+                </a-input>
+            </a-input-group>
+        </a-form-item>
+        <a-form-item>
+            <template slot="label">
                 <a-tooltip>
                     <template slot="title">
                         <span>{{ i18n "pages.xray.rules.useComma" }}</span>
                     </template>
-                    <a-icon type="question-circle"></a-icon>
+                    IP <a-icon type="question-circle"></a-icon>
                 </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="ruleModal.rule.ip" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Domain
+            </template>
+            <a-input v-model.trim="ruleModal.rule.ip"></a-input>
+        </a-form-item>
+        <a-form-item>
+            <template slot="label">
                 <a-tooltip>
                     <template slot="title">
                         <span>{{ i18n "pages.xray.rules.useComma" }}</span>
                     </template>
-                    <a-icon type="question-circle"></a-icon>
+                    Domain <a-icon type="question-circle"></a-icon>
                 </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="ruleModal.rule.domain" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Port
+            </template>
+            <a-input v-model.trim="ruleModal.rule.domain"></a-input>
+        </a-form-item>
+        <a-form-item>
+            <template slot="label">
                 <a-tooltip>
                     <template slot="title">
                         <span>{{ i18n "pages.xray.rules.useComma" }}</span>
                     </template>
-                    <a-icon type="question-circle"></a-icon>
+                    Port <a-icon type="question-circle"></a-icon>
                 </a-tooltip>
-            </td>
-            <td>
-                <a-form-item>
-                    <a-input v-model.trim="ruleModal.rule.port" style="width: 250px"></a-input>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Inbound Tags</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="ruleModal.rule.inboundTag" mode="multiple" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
-        <tr>
-            <td>Outbound Tag</td>
-            <td>
-                <a-form-item>
-                    <a-select v-model="ruleModal.rule.outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
-                        <a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
-                    </a-select>
-                </a-form-item>
-            </td>
-        </tr>
+            </template>
+            <a-input v-model.trim="ruleModal.rule.port"></a-input>
+        </a-form-item>
+        <a-form-item label='Inbound Tags'>
+            <a-select v-model="ruleModal.rule.inboundTag" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label='Outbound Tag'>
+            <a-select v-model="ruleModal.rule.outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
+            </a-select>
+        </a-form-item>
     </table>
     </a-form>
 </a-modal>
@@ -274,4 +225,4 @@
     });
 
 </script>
-{{end}}
+{{end}}