瀏覽代碼

en lang edit, new designed

MHSanaei 2 年之前
父節點
當前提交
6d28c39ae8

+ 1 - 2
.gitignore

@@ -1,7 +1,6 @@
 .idea
 tmp
-bin/xray-darwin-arm64
-bin/config.json
+bin/
 dist/
 x-ui-*.tar.gz
 /x-ui

+ 1 - 1
config/version

@@ -1 +1 @@
-0.5.3
+1.0.4

+ 2 - 2
web/assets/js/model/models.js

@@ -151,9 +151,9 @@ class DBInbound {
         }
     }
 
-    genLink() {
+    genLink(clientIndex) {
         const inbound = this.toInbound();
-        return inbound.genLink(this.address, this.remark);
+        return inbound.genLink(this.address, this.remark, clientIndex);
     }
 }
 

+ 15 - 6
web/assets/js/model/xray.js

@@ -1040,7 +1040,6 @@ class Inbound extends XrayCommonClass {
             }
         }
 		
-		remark = this.settings.vmesses[clientIndex].email ?? remark;
         let obj = {
             v: '2',
             ps: remark,
@@ -1063,7 +1062,6 @@ class Inbound extends XrayCommonClass {
         const port = this.port;
         const type = this.stream.network;
         const params = new Map();
-		remark = settings.vlesses[clientIndex].email ?? remark;
         params.set("type", this.stream.network);
         if (this.xtls) {
             params.set("security", "xtls");
@@ -1156,7 +1154,6 @@ class Inbound extends XrayCommonClass {
         const port = this.port;
         const type = this.stream.network;
         const params = new Map();
-		remark = settings.trojans[clientIndex].email ?? remark;
         params.set("type", this.stream.network);
         if (this.xtls) {
             params.set("security", "xtls");
@@ -1229,10 +1226,22 @@ class Inbound extends XrayCommonClass {
 
     genLink(address='', remark='', clientIndex=0) {
         switch (this.protocol) {
-            case Protocols.VMESS: return this.genVmessLink(address, remark, clientIndex);
-            case Protocols.VLESS: return this.genVLESSLink(address, remark, clientIndex);
+            case Protocols.VMESS:
+                if (this.settings.vmesses[clientIndex].email != ""){
+                    remark += '-' + this.settings.vmesses[clientIndex].email
+                }
+                return this.genVmessLink(address, remark, clientIndex);
+            case Protocols.VLESS:
+                if (this.settings.vlesses[clientIndex].email != ""){
+                    remark += '-' + this.settings.vlesses[clientIndex].email
+                }
+                return this.genVLESSLink(address, remark, clientIndex);
             case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark);
-            case Protocols.TROJAN: return this.genTrojanLink(address, remark, clientIndex);
+            case Protocols.TROJAN:
+                if (this.settings.trojans[clientIndex].email != ""){
+                    remark += '-' + this.settings.trojans[clientIndex].email
+                }
+                return this.genTrojanLink(address, remark, clientIndex);
             default: return '';
         }
     }

+ 13 - 1
web/assets/js/util/common.js

@@ -54,4 +54,16 @@ function addZero(num) {
 function toFixed(num, n) {
     n = Math.pow(10, n);
     return Math.round(num * n) / n;
-}
+}
+
+function debounce (fn, delay) {
+    var timeoutID = null
+    return function () {
+      clearTimeout(timeoutID)
+      var args = arguments
+      var that = this
+      timeoutID = setTimeout(function () {
+        fn.apply(that, args)
+      }, delay)
+    }
+  }

+ 1 - 71
web/html/common/qrcode_modal.html

@@ -2,26 +2,7 @@
 <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
          :closable="true" width="300px" :ok-text="qrModal.okText"
          cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}">
-    <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >click on QR Code to Copy</a-tag>
-    <canvas v-if="qrModal.inbound.protocol != Protocols.VMESS && qrModal.inbound.protocol != Protocols.VLESS && qrModal.inbound.protocol != Protocols.TROJAN" id="qrCode" style="width: 100%; height: 100%;"></canvas>
-
-    <template v-if="qrModal.inbound.protocol === Protocols.VMESS" v-for="(vmess, index) in qrModal.inbound.settings.vmesses">
-        <a-tag color="red" style="margin-bottom: 10px;display: block;text-align: center;" v-text="vmess.email"></a-tag>
-        <canvas @click="copyTextToClipboard(`qrCode-vmess-${vmess.id}`,index)" :id="`qrCode-vmess-${vmess.id}`" style="width: 100%; height: 100%;"></canvas>
-        <a-divider style="height: 2px; background-color: #7e7e7e" />
-    </template>
-
-    <template v-if="qrModal.inbound.protocol === Protocols.VLESS" v-for="(vless, index) in qrModal.inbound.settings.vlesses">
-        <a-tag color="red" style="margin-bottom: 10px;display: block;text-align: center;" v-text="vless.email"></a-tag>
-        <canvas @click="copyTextToClipboard(`qrCode-vless-${vless.id}`,index)" :id="`qrCode-vless-${vless.id}`" style="width: 100%; height: 100%;"></canvas>
-        <a-divider style="height: 2px; background-color: #7e7e7e" />
-    </template>
-    
-    <template v-if="qrModal.inbound.protocol === Protocols.TROJAN" v-for="(trojan, index) in qrModal.inbound.settings.trojans">
-        <a-tag color="red" style="margin-bottom: 10px;display: block;text-align: center;" v-text="trojan.email"></a-tag>
-        <canvas @click="copyTextToClipboard(`qrCode-trojan-${trojan.password}`,index)" :id="`qrCode-trojan-${trojan.password}`" style="width: 100%; height: 100%;"></canvas>
-        <a-divider style="height: 2px; background-color: #7e7e7e" />
-    </template>
+		<canvas id="qrCode" style="width: 100%; height: 100%;"></canvas>
 </a-modal>
 
 <script>
@@ -76,57 +57,6 @@
         data: {
             qrModal: qrModal,
         },
-        methods: {
-            setQrCode(elmentId,index) {
-                content = qrModal.inbound.genLink(qrModal.dbInbound.address,qrModal.dbInbound.remark,index)
-
-                new QRious({
-                        element: document.querySelector('#'+elmentId),
-                        size: 260,
-                        value: content,
-                    });
-            },
-            copyTextToClipboard(elmentId,index) {
-                link = qrModal.inbound.genLink(qrModal.dbInbound.address,qrModal.dbInbound.remark,index)
-                this.qrModal.copyText = link
-
-                this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
-                        text: () => link,
-                    });
-                this.qrModal.clipboard.on('success', () => { 
-                    app.$message.success('{{ i18n "copied" }}')
-                    this.qrModal.clipboard.destroy();
-                });
-
-
-            }
-        },
-        updated()  {
-            switch (qrModal.inbound.protocol) {
-                    case Protocols.VMESS: 
-                        vmesses = qrModal.inbound.settings.vmesses
-                        for (const index in vmesses) {
-                            this.setQrCode("qrCode-vmess-" + vmesses[index].id ,index)
-                        }
-                    break;
-                    case Protocols.VLESS: 
-                        vlesses = qrModal.inbound.settings.vlesses
-
-                        for (const index in vlesses) {
-                            this.setQrCode("qrCode-vless-" + vlesses[index].id ,index)
-                        }
-                    break;
-                    case Protocols.TROJAN: 
-                        trojans = qrModal.inbound.settings.trojans
-
-                        for (const index in trojans) {
-                            this.setQrCode("qrCode-trojan-" + trojans[index].password ,index)
-                        }
-                    break;
-                    default: return null;
-            }
-
-        }
     });
 
 </script>

+ 10 - 10
web/html/xui/form/protocol/trojan.html

@@ -12,7 +12,7 @@
                 Email
                 <a-tooltip>
                     <template slot="title">
-                        The email must be completely unique
+                        The Email Must Be Completely Unique
                     </template>
                     <!--Renew Svg Icon-->
                     <svg 
@@ -23,10 +23,10 @@
             <a-input v-model.trim="trojan.email"></a-input>
         </a-form-item>
     </a-form>
-    <a-form-item label="password">
+    <a-form-item label="Password">
         <a-input v-model.trim="trojan.password"></a-input>
     </a-form-item>
-        <a-form-item v-if="inbound.xtls" label="flow">
+        <a-form-item v-if="inbound.xtls" label="Flow">
             <a-select v-model="inbound.settings.trojans[index].flow" style="width: 150px">
                 <a-select-option value="" selected>none</a-select-option>
                 <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
@@ -60,7 +60,7 @@
     <a-form layout="inline">
         <a-tooltip v-if="trojan._totalGB > 0">
             <template slot="title">
-                reset traffic
+                {{ i18n "pages.inbounds.resetTraffic" }}
             </template>
             <span style="color: #FF4D4F">
                 <a-icon type="delete" @click="resetClientTraffic(trojan,$event)"></a-icon>
@@ -104,7 +104,7 @@
        </a-form>
     </a-form>
 <a-form layout="inline">
-    <a-form-item label="fallbacks">
+    <a-form-item label="Fallbacks">
          <a-row>
               <a-button type="primary" size="small"
                   @click="inbound.settings.addTrojanFallback()">
@@ -121,19 +121,19 @@
     <a-icon type="delete" @click="() => inbound.settings.delTrojanFallback(index)"
             style="color: rgb(255, 77, 79);cursor: pointer;"/>
 </a-divider>
-<a-form-item label="name">
+<a-form-item label="Name">
     <a-input v-model="fallback.name"></a-input>
 </a-form-item>
-<a-form-item label="alpn">
+<a-form-item label="Alpn">
     <a-input v-model="fallback.alpn"></a-input>
 </a-form-item>
-<a-form-item label="path">
+<a-form-item label="Path">
     <a-input v-model="fallback.path"></a-input>
 </a-form-item>
-<a-form-item label="dest">
+<a-form-item label="Dest">
     <a-input v-model="fallback.dest"></a-input>
 </a-form-item>
-<a-form-item label="xver">
+<a-form-item label="xVer">
     <a-input type="number" v-model.number="fallback.xver"></a-input>
 </a-form-item>
 <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>

+ 9 - 9
web/html/xui/form/protocol/vless.html

@@ -12,7 +12,7 @@
                     Email
                     <a-tooltip>
                         <template slot="title">
-                            The email must be completely unique
+                            The Email Must Be Completely Unique
                         </template>
                         <!--Renew Svg Icon-->
                         <svg 
@@ -23,7 +23,7 @@
                 <a-input v-model.trim="vless.email"></a-input>
             </a-form-item>
         </a-form>
-        <a-form-item label="id">
+        <a-form-item label="ID">
             <a-input v-model.trim="vless.id"></a-input>
         </a-form-item>
 		<a-form-item v-if="inbound.xtls" label="flow">
@@ -71,7 +71,7 @@
         <a-form layout="inline">
             <a-tooltip v-if="vless._totalGB > 0">
                 <template slot="title">
-                    reset traffic
+                   {{ i18n "pages.inbounds.resetTraffic" }}
                 </template>
                 <span style="color: #FF4D4F">
                     <a-icon type="delete" @click="resetClientTraffic(vless,$event)"></a-icon>
@@ -118,7 +118,7 @@
     </a-form>
 
 <a-form layout="inline">
-    <a-form-item label="fallbacks">
+    <a-form-item label="Fallbacks">
         <a-row>
             <a-button type="primary" size="small"
                       @click="inbound.settings.addFallback()">
@@ -135,19 +135,19 @@
         <a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
                 style="color: rgb(255, 77, 79);cursor: pointer;"/>
     </a-divider>
-    <a-form-item label="name">
+    <a-form-item label="Name">
         <a-input v-model="fallback.name"></a-input>
     </a-form-item>
-    <a-form-item label="alpn">
+    <a-form-item label="Alpn">
         <a-input v-model="fallback.alpn"></a-input>
     </a-form-item>
-    <a-form-item label="path">
+    <a-form-item label="Path">
         <a-input v-model="fallback.path"></a-input>
     </a-form-item>
-    <a-form-item label="dest">
+    <a-form-item label="Dest">
         <a-input v-model="fallback.dest"></a-input>
     </a-form-item>
-    <a-form-item label="xver">
+    <a-form-item label="xVer">
         <a-input type="number" v-model.number="fallback.xver"></a-input>
     </a-form-item>
     <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>

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

@@ -11,7 +11,7 @@
                     Email
                     <a-tooltip>
                         <template slot="title">
-                            The email must be completely unique
+                            The Email Must Be Completely Unique
                         </template>
                         <!--Renew Svg Icon-->
                         <svg 
@@ -22,7 +22,7 @@
                 <a-input v-model.trim="vmess.email"></a-input>
             </a-form-item>
         </a-form>
-        <a-form-item label="id">
+        <a-form-item label="ID">
             <a-input v-model.trim="vmess.id"></a-input>
         </a-form-item>
         <a-form-item label='{{ i18n "additional" }} ID'>
@@ -56,7 +56,7 @@
         <a-form layout="inline">
             <a-tooltip v-if="vmess._totalGB > 0">
                 <template slot="title">
-                    reset traffic
+                  {{ i18n "pages.inbounds.resetTraffic" }}
                 </template>
                 <span style="color: #FF4D4F">
                     <a-icon type="delete" @click="resetClientTraffic(vmess,$event)"></a-icon>

+ 1 - 1
web/html/xui/form/sniffing.html

@@ -2,7 +2,7 @@
 <a-form layout="inline">
   <a-form-item>
             <span slot="label">
-                sniffing
+                Sniffing
                 <a-tooltip>
                     <template slot="title">
                         <span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>

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

@@ -1,6 +1,6 @@
 {{define "form/streamGRPC"}}
 <a-form layout="inline">
-    <a-form-item label="serviceName">
+    <a-form-item label="ServiceName">
         <a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
     </a-form-item>
 </a-form>

+ 1 - 1
web/html/xui/form/stream/stream_http.html

@@ -3,7 +3,7 @@
     <a-form-item label='{{ i18n "path" }}'>
         <a-input v-model.trim="inbound.stream.http.path"></a-input>
     </a-form-item>
-    <a-form-item label="host">
+    <a-form-item label="Host">
         <a-row v-for="(host, index) in inbound.stream.http.host">
             <a-input v-model.trim="inbound.stream.http.host[index]"></a-input>
         </a-row>

+ 7 - 7
web/html/xui/form/stream/stream_kcp.html

@@ -13,25 +13,25 @@
     <a-form-item label='{{ i18n "password" }}'>
         <a-input v-model.number="inbound.stream.kcp.seed"></a-input>
     </a-form-item>
-    <a-form-item label="mtu">
+    <a-form-item label="MTU">
         <a-input type="number" v-model.number="inbound.stream.kcp.mtu"></a-input>
     </a-form-item>
-    <a-form-item label="tti (ms)">
+    <a-form-item label="TTI (ms)">
         <a-input type="number" v-model.number="inbound.stream.kcp.tti"></a-input>
     </a-form-item>
-    <a-form-item label="uplink capacity (MB/S)">
+    <a-form-item label="Uplink Capacity (MB/S)">
         <a-input type="number" v-model.number="inbound.stream.kcp.upCap"></a-input>
     </a-form-item>
-    <a-form-item label="downlink capacity (MB/S)">
+    <a-form-item label="Downlink Capacity (MB/S)">
         <a-input type="number" v-model.number="inbound.stream.kcp.downCap"></a-input>
     </a-form-item>
-    <a-form-item label="congestion">
+    <a-form-item label="Congestion">
         <a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
     </a-form-item>
-    <a-form-item label="read buffer size (MB)">
+    <a-form-item label="Read Buffer Size (MB)">
         <a-input type="number" v-model.number="inbound.stream.kcp.readBuffer"></a-input>
     </a-form-item>
-    <a-form-item label="write buffer size (MB)">
+    <a-form-item label="Write Buffer Size (MB)">
         <a-input type="number" v-model.number="inbound.stream.kcp.writeBuffer"></a-input>
     </a-form-item>
 </a-form>

+ 6 - 6
web/html/xui/form/stream/stream_tcp.html

@@ -1,19 +1,19 @@
 {{define "form/streamTCP"}}
 <!-- tcp type -->
 <a-form layout="inline">
-    <a-form-item label="acceptProxyProtocol">
+    <a-form-item label="AcceptProxyProtocol">
         <a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
     </a-form-item>
-    <a-form-item label="http camouflage">
+    <a-form-item label="HTTP 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'"
+<a-form v-if="inbound.stream.tcp.type === 'HTTP'"
         layout="inline">
     <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
         <a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
@@ -50,7 +50,7 @@
 </a-form>
 
 <!-- tcp response -->
-<a-form v-if="inbound.stream.tcp.type === 'http'"
+<a-form v-if="inbound.stream.tcp.type === 'HTTP'"
         layout="inline">
     <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
         <a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>

+ 1 - 1
web/html/xui/form/stream/stream_ws.html

@@ -1,6 +1,6 @@
 {{define "form/streamWS"}}
 <a-form layout="inline">
-    <a-form-item label="acceptProxyProtocol">
+    <a-form-item label="AcceptProxyProtocol">
         <a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
     </a-form-item>
 </a-form>

+ 6 - 6
web/html/xui/form/tls_settings.html

@@ -1,28 +1,28 @@
 {{define "form/tlsSettings"}}
 <!-- tls enable -->
 <a-form layout="inline" v-if="inbound.canSetTls()">
-    <a-form-item label="tls">
+    <a-form-item label="TLS">
         <a-switch v-model="inbound.tls">
         </a-switch>
     </a-form-item>
-    <a-form-item v-if="inbound.canEnableXTls()" label="xtls">
+    <a-form-item v-if="inbound.canEnableXTls()" label="XTLS">
         <a-switch v-model="inbound.xtls"></a-switch>
     </a-form-item>
 </a-form>
 
 <!-- tls settings -->
 <a-form v-if="inbound.tls || inbound.xtls"layout="inline">
-    <a-form-item label="minVersion">
+    <a-form-item label="MinVersion">
         <a-select v-model="inbound.stream.tls.minVersion" style="width: 60px">
             <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
         </a-select>
     </a-form-item>
-    <a-form-item label="maxVersion">
+    <a-form-item label="MaxVersion">
         <a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px">
             <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
         </a-select>
     </a-form-item>
-    <a-form-item label="cipherSuites">
+    <a-form-item label="CipherSuites">
         <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
             <a-select-option value="">auto</a-select-option>
             <a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
@@ -31,7 +31,7 @@
     <a-form-item label='{{ i18n "domainName" }}'>
         <a-input v-model.trim="inbound.stream.tls.server"></a-input>
     </a-form-item>
-    <a-form-item label="alpn">
+    <a-form-item label="Alpn">
         <a-input v-model.trim="inbound.stream.tls.alpn"></a-input>
     </a-form-item>
     <a-form-item label='{{ i18n "certificate" }}'>

+ 93 - 25
web/html/xui/inbound_info_modal.html

@@ -1,9 +1,65 @@
 {{define "inboundInfoModal"}}
-{{template "component/inboundInfo"}}
-<a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' @ok="infoModal.ok"
-         :closable="true" :mask-closable="true"
-         ok-text='{{ i18n "pages.inbounds.copyLink"}}' cancel-text='{{ i18n "close" }}' :ok-button-props="infoModal.okBtnPros">
-    <inbound-info :db-inbound="dbInbound" :inbound="inbound"></inbound-info>
+<a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
+         :closable="true"
+         :mask-closable="true"
+         :footer="null"
+         >
+    <table style="margin-bottom: 10px; width: 100%;">
+        <tr><td>
+            <table>
+                <tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
+                <tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
+                <tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ 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 v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
+                <tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
+            
+                <tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr>
+                <tr v-else><td>{{ i18n "path" }}</td><td><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 color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr>
+                <tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr>
+                <tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr>
+            </template>
+            
+            <template v-if="inbound.isKcp">
+                <tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr>
+                <tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr>
+            </template>
+            
+            <template v-if="inbound.isGrpc">
+                <tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
+            </template>
+            </table>
+        </td></tr>
+            <tr colspan="2">
+                <td v-if="inbound.tls">
+                    tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
+                    tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
+                </td>
+                <td v-else-if="inbound.xtls">
+                    xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
+                    xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
+                </td>
+                <td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
+            </td>
+        </tr>
+    </table>
+    <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
+    <template v-if="dbInbound.hasLink()">
+        <p>Client URL:</p>
+        <p>[[ infoModal.link ]]</p>
+        <button class="btn" id="copy-url-link"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
+    </template>
 </a-modal>
 <script>
 
@@ -12,31 +68,22 @@
         inbound: new Inbound(),
         dbInbound: new DBInbound(),
         clipboard: null,
-        okBtnPros: {
-            attrs: {
-                id: "inbound-info-modal-ok-btn",
-                style: "",
-            },
-        },
-        show(dbInbound) {
+        link: null,
+        index: 0,
+        show(dbInbound, index=0) {
+            this.index = index;
             this.inbound = dbInbound.toInbound();
             this.dbInbound = new DBInbound(dbInbound);
+            this.link = dbInbound.genLink(index);
             this.visible = true;
-
-            if (dbInbound.hasLink()) {
-                this.okBtnPros.attrs.style = "";
-            } else {
-                this.okBtnPros.attrs.style = "display: none";
-            }
-
-            if (this.clipboard == null) {
-                infoModalApp.$nextTick(() => {
-                    this.clipboard = new ClipboardJS(`#${this.okBtnPros.attrs.id}`, {
-                        text: () => this.dbInbound.genLink(),
+            infoModalApp.$nextTick(() => {
+                if (this.clipboard === null) {
+                    this.clipboard = new ClipboardJS('#copy-url-link', {
+                        text: () => this.link,
                     });
                     this.clipboard.on('success', () => app.$message.success('{{ i18n "copySuccess" }}'));
-                });
-            }
+                }
+            });
         },
         close() {
             infoModal.visible = false;
@@ -55,6 +102,27 @@
                 return this.infoModal.inbound;
             }
         },
+        methods: {
+            setQrCode(elmentId,index) {
+                content = infoModal.inbound.genLink(infoModal.dbInbound.address,infoModal.dbInbound.remark,index)
+
+                new QRious({
+                        element: document.querySelector('#'+elmentId),
+                        size: 260,
+                        value: content,
+                    });
+            },
+            copyTextToClipboard(elmentId,content) {
+                this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
+                        text: () => content,
+                    });
+                this.infoModal.clipboard.on('success', () => { 
+                    app.$message.success('{{ i18n "copySuccess" }}')
+                    this.infoModal.clipboard.destroy();
+                });
+            }
+        },
+        
     });
 
 </script>

+ 87 - 29
web/html/xui/inbounds.html

@@ -46,10 +46,10 @@
                         <div slot="title">
                              <a-button type="primary" @click="openAddInbound">Add Inbound</a-button>
                         </div>
-<!--                        <a-input v-model="searchKey" placeholder="search" autofocus style="max-width: 300px"></a-input>-->
+						<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
                         <a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
-                                 :data-source="dbInbounds"
-                                 :loading="spinning" :scroll="{ x: 1500 }"
+                                 :data-source="searchedInbounds"
+                                 :loading="spinning" :scroll="{ x: 1300 }"
                                  :pagination="false"
                                  style="margin-top: 20px"
                                  @change="() => getDBInbounds()">
@@ -58,7 +58,7 @@
                                 <a-dropdown :trigger="['click']">
                                     <a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
                                     <a-menu slot="overlay" @click="a => clickAction(a, dbInbound)">
-                                        <a-menu-item v-if="dbInbound.hasLink()" key="qrcode">
+                                        <a-menu-item v-if="dbInbound.isSS" key="qrcode">
                                             <a-icon type="qrcode"></a-icon>
                                             {{ i18n "qrCode" }}
                                         </a-menu-item>
@@ -88,9 +88,6 @@
                                 </template>
                                 <a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
                             </template>
-                            <template slot="settings" slot-scope="text, dbInbound">
-                                <a-button type="link" @click="showInfo(dbInbound)">{{ i18n "check" }}</a-button>
-                            </template>
                             <template slot="stream" slot-scope="text, dbInbound, index">
                                 <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
                                     <a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
@@ -115,13 +112,31 @@
                             </template>
                             <template slot="expandedRowRender" slot-scope="record">
                                 <a-table
-                                v-if="(record.protocol === Protocols.VLESS) || (record.protocol === Protocols.VMESS) || (record.protocol === Protocols.TROJAN)"
+                                v-if="(record.protocol === Protocols.VLESS) || (record.protocol === Protocols.VMESS)"
                                 :row-key="client => client.id"
                                 :columns="innerColumns"
                                 :data-source="getInboundClients(record)"
                                 :pagination="false"
                                 >
-                                    {{template "form/client_row"}}
+                                    {{template "client_row"}}
+                                </a-table>
+                                <a-table
+                                v-else-if="record.protocol === Protocols.TROJAN"
+                                :row-key="client => client.id"
+                                :columns="innerTrojanColumns"
+                                :data-source="getInboundClients(record)"
+                                :pagination="false"
+                                >
+                                    {{template "client_row"}}
+                                </a-table>
+                                <a-table
+                                v-else
+                                :row-key="client => client.id"
+                                :columns="innerOneColumns"
+                                :data-source="record"
+                                :pagination="false"
+                                >
+                                    {{template "client_row"}}
                                 </a-table>
                             </template>
                         </a-table>
@@ -152,29 +167,24 @@
     }, {
         title: '{{ i18n "pages.inbounds.remark" }}',
         align: 'center',
-        width: 100,
+        width: 60,
         dataIndex: "remark",
     }, {
         title: '{{ i18n "pages.inbounds.protocol" }}',
         align: 'center',
-        width: 60,
+        width: 40,
         scopedSlots: { customRender: 'protocol' },
     }, {
         title: '{{ i18n "pages.inbounds.port" }}',
         align: 'center',
         dataIndex: "port",
-        width: 60,
+        width: 40,
     }, {
         title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
         align: 'center',
         width: 150,
         scopedSlots: { customRender: 'traffic' },
-    }, {
-        title: '{{ i18n "pages.inbounds.details" }}',
-        align: 'center',
-        width: 40,
-        scopedSlots: { customRender: 'settings' },
-    }, {
+    },{
         title: '{{ i18n "pages.inbounds.transportConfig" }}',
         align: 'center',
         width: 60,
@@ -187,10 +197,23 @@
     }];
 
     const innerColumns = [
-    { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
-    { title: '{{ i18n "pages.inbounds.traffic" }}', width: 100, scopedSlots: { customRender: 'traffic' } },
-    { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, scopedSlots: { customRender: 'expiryTime' } },
-    { title: '{{ i18n "pages.inbounds.uid" }}', width: 150, dataIndex: "id" },
+        { title: '', width: 50, scopedSlots: { customRender: 'actions' } },
+        { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
+        { title: '{{ i18n "pages.inbounds.traffic" }}', width: 100, scopedSlots: { customRender: 'traffic' } },
+        { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, scopedSlots: { customRender: 'expiryTime' } },
+        { title: 'UID', width: 150, dataIndex: "id" },
+    ];
+
+    const innerTrojanColumns = [
+        { title: '', width: 50, scopedSlots: { customRender: 'actions' } },
+        { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
+        { title: '{{ i18n "pages.inbounds.traffic" }}', width: 100, scopedSlots: { customRender: 'traffic' } },
+        { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, scopedSlots: { customRender: 'expiryTime' } },
+        { title: 'Password', width: 150, dataIndex: "password" },
+    ];
+
+    const innerOneColumns = [
+        { title: '', width: 50, scopedSlots: { customRender: 'actions' } },
     ];
 
     const app = new Vue({
@@ -202,6 +225,7 @@
             inbounds: [],
             dbInbounds: [],
             searchKey: '',
+            searchedInbounds: [],
         },
         methods: {
             loading(spinning=true) {
@@ -219,10 +243,12 @@
             setInbounds(dbInbounds) {
                 this.inbounds.splice(0);
                 this.dbInbounds.splice(0);
+                this.searchedInbounds.splice(0);
                 for (const inbound of dbInbounds) {
                     const dbInbound = new DBInbound(inbound);
                     this.inbounds.push(dbInbound.toInbound());
                     this.dbInbounds.push(dbInbound);
+                    this.searchedInbounds.push(dbInbound);
                 }
             },
             searchInbounds(key) {
@@ -341,12 +367,12 @@
                     onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
                 });
             },
-            showQrcode(dbInbound) {
-                const link = dbInbound.genLink();
+            showQrcode(dbInbound, clientIndex) {
+                const link = dbInbound.genLink(clientIndex);
                 qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
             },
-            showInfo(dbInbound) {
-                infoModal.show(dbInbound);
+            showInfo(dbInbound, index) {
+                infoModal.show(dbInbound, index);
             },
             switchEnable(dbInbound) {
                 this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
@@ -366,6 +392,35 @@
                     return dbInbound.toInbound().settings.trojans
                 }
             },
+            resetClientTraffic(client,inbound,event) {
+                this.$confirm({
+                    title: '{{ i18n "pages.inbounds.resetTraffic"}}',
+                    content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
+                    okText: '{{ i18n "reset"}}',
+                    cancelText: '{{ i18n "cancel"}}',
+                    onOk: () => {
+                        this.resetClTraffic(client,inbound,event);
+                    },
+                });
+            },
+            async resetClTraffic(client,inbound,event) {
+                const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
+                if (!msg.success) {
+                    return;
+                }
+                clientStats = inbound.clientStats
+                if(clientStats.length > 0)
+                {
+                    for (const key in clientStats) {
+                        if (Object.hasOwnProperty.call(clientStats, key)) {
+                            if(clientStats[key]['email'] == client.email){ 
+                                clientStats[key]['up'] = 0
+                                clientStats[key]['down'] = 0
+                            }
+                        }
+                    }
+                }
+            },
             isExpiry(dbInbound, index) {
                 return dbInbound.toInbound().isExpiry(index)
             },
@@ -421,12 +476,15 @@
                         }
                     }
                 }
+                 else{
+                    return true
+                }
             },
         },
         watch: {
-            searchKey(value) {
-                this.searchInbounds(value);
-            }
+            searchKey: debounce(function (newVal) {
+                this.searchInbounds(newVal);
+            }, 500)
         },
         mounted() {
             this.getDBInbounds();

+ 15 - 3
web/html/xui/inbounds_client_row.html

@@ -1,8 +1,20 @@
-{{define "form/client_row"}}
+{{define "client_row"}}
+<template slot="actions" slot-scope="text, client, index">
+    <a-dropdown>
+        <a-icon @click="e => e.preventDefault()" type="menu"></a-icon>
+        <template #overlay>
+          <a-menu>
+            <a-menu-item v-if="record.hasLink()" @click="showQrcode(record,index);"><a-icon type="qrcode"></a-icon>{{ i18n "qrCode" }}</a-menu-item>
+            <a-menu-item @click="showInfo(record,index);"><a-icon type="info-circle"></a-icon>{{ i18n "info" }}</a-menu-item>
+            <a-menu-item @click="resetClientTraffic(client,record,$event)" v-if="client.email != ''"><a-icon type="retweet"></a-icon>{{ i18n "pages.inbounds.resetTraffic" }}</a-menu-item>
+          </a-menu>
+        </template>
+    </a-dropdown>
+</template>
 <template slot="client" slot-scope="text, client">
     [[ client.email ]]
-    <a-tag v-if="!isClientEnabled(record, client.email)" color="red"> expired</a-tag>
-</template>                                    
+    <a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "disabled" }}</a-tag>
+</template>                                  
 <template slot="traffic" slot-scope="text, client">
     <a-tag v-if="client._totalGB === 0" color="blue">{{ i18n "used" }}: [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]]</a-tag>
     <a-tag v-if="client._totalGB > 0 && !isTrafficExhausted(record, client.email)" color="green">{{ i18n "used" }}: [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] / [[client._totalGB]]GB</a-tag>

+ 17 - 13
web/translation/translate.en_US.toml

@@ -42,6 +42,9 @@
 "install" = "Install"
 "used" = "Used"
 "clients" = "Clients"
+"search" = "Search"
+"usage" = "Usage"
+"info" = "Details"
 
 [menu]
 "dashboard" = "System Status"
@@ -56,8 +59,8 @@
 
 [pages.login.toasts]
 "invalidFormData" = "Input Data Format Is Invalid"
-"emptyUsername" = "please Enter Username"
-"emptyPassword" = "please Enter Password"
+"emptyUsername" = "Please Enter Username"
+"emptyPassword" = "Please Enter Password"
 "wrongUsernameOrPassword" = "invalid username or password"
 "successLogin" = "Login"
 
@@ -112,7 +115,7 @@
 "targetAddress" = "Target Address"
 "disableInsecureEncryption" = "Disable insecure encryption"
 "monitorDesc" = "Leave blank by default"
-"meansNoLimit" = "means no limit"
+"meansNoLimit" = "Means No Limit"
 "totalFlow" = "Total Traffic"
 "leaveBlankToNeverExpire" = "Leave blank to never expire"
 "noRecommendKeepDefault" = "There are no special requirements to keep the default"
@@ -125,22 +128,23 @@
 "client" = "Client"
 "uid" = "UID"
 
+
 [pages.inbounds.toasts]
 "obtain" = "Obtain"
 
 [pages.inbounds.stream.general]
-"requestHeader" = "request header"
-"name" = "name"
-"value" = "value"
+"requestHeader" = "Request Header"
+"name" = "Name"
+"value" = "Value"
 
 [pages.inbounds.stream.tcp]
-"requestVersion" = "request version"
-"requestMethod" = "request method"
-"requestPath" = "request path"
-"responseVersion" = "response version"
-"responseStatus" = "response status"
-"responseStatusDescription" = "response status description"
-"responseHeader" = "response header"
+"requestVersion" = "Request Version"
+"requestMethod" = "Request Method"
+"requestPath" = "Request Path"
+"responseVersion" = "Response Version"
+"responseStatus" = "Response Status"
+"responseStatusDescription" = "Response Status Description"
+"responseHeader" = "Response Header"
 
 [pages.inbounds.stream.quic]
 "encryption" = "Encryption"

+ 3 - 1
web/translation/translate.fa_IR.toml

@@ -10,7 +10,6 @@
 "remark" = "نام"
 "enable" = "فعال"
 "protocol" = "پروتکل"
-
 "loading" = "در حال بروزرسانی..."
 "second" = "ثانیه"
 "minute" = "دقیقه"
@@ -43,6 +42,9 @@
 "install" = "نصب"
 "used" = "استفاده شده"
 "clients" = "کاربران"
+"search" = "جستجو"
+"usage" = "استفاده"
+"info" = "جزئیات"
 
 [menu]
 "dashboard" = "وضعیت سیستم"

+ 3 - 1
web/translation/translate.zh_Hans.toml

@@ -10,7 +10,6 @@
 "remark" = "备注"
 "enable" = "启用"
 "protocol" = "协议"
-
 "loading" = "加载中"
 "second" = "秒"
 "minute" = "分钟"
@@ -43,6 +42,9 @@
 "install" = "安装"
 "used" = "用过的"
 "clients" = "客户端"
+"search" = "搜索"
+"usage" = "用法"
+"info" = "细节"
 
 [menu]
 "dashboard" = "系统状态"