MHSanaei 2 years ago
parent
commit
bc56e63737

File diff suppressed because it is too large
+ 0 - 0
web/assets/[email protected]/antd.min.css


+ 61 - 0
web/assets/css/custom.css

@@ -150,3 +150,64 @@
     color:rgb(255, 255, 255) !important;
     background-color: rgb(255, 127, 127);
 }
+
+.ant-card-dark {
+    color: hsla(0,0%,100%,.65);
+    background-color: #1a212a;
+    border-color:rgba(0,0,0,.09);
+}
+
+.ant-card-dark:hover {
+    border-color: #e8e8e8;
+}
+
+.ant-card-dark .ant-table-thead th {
+    color: hsla(0,0%,100%,.65);
+    background-color: #1b202b;
+}
+
+.ant-card-dark .ant-table-tbody tr td,
+.ant-card-dark .ant-modal-title {
+    color: hsla(0,0%,100%,.65);
+}
+
+.ant-card-dark .ant-collapse-content {
+    background-color: #1a212a;
+}
+
+.ant-card-dark .ant-list-item-meta-title,
+.ant-card-dark .ant-list-item-meta-description,
+.ant-card-dark .ant-form-item-label>label,
+.ant-card-dark .ant-form-item,
+.ant-card-dark .ant-divider-inner-text,
+.ant-card-dark .ant-collapse>.ant-collapse-item>.ant-collapse-header {
+    color: hsla(0,0%,100%,.65);
+}
+
+.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td {
+    background-color: #004488;
+}
+
+.ant-card-dark tbody .ant-table-expanded-row {
+    color: hsla(0,0%,100%,.65);
+    background-color: #023366; 
+}
+
+.ant-card-dark .ant-input,
+.ant-card-dark .ant-input-number,
+.ant-card-dark .ant-select-selection {
+    background-color: #023366;
+    color: hsla(0,0%,100%,.65);
+}
+
+.ant-card-dark .ant-collapse-item {
+    background-color: #1b202b;
+    color: hsla(0,0%,100%,.65);
+}
+
+.ant-card-dark .ant-modal-content,
+.ant-card-dark .ant-modal-body,
+.ant-card-dark .ant-modal-header {
+    color: hsla(0,0%,100%,.65);
+    background-color: #242c3a; 
+}

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

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

+ 118 - 45
web/assets/js/model/xray.js

@@ -57,7 +57,7 @@ const TLS_VERSION_OPTION = {
     TLS11: "1.1",
     TLS12: "1.2",
     TLS13: "1.3",
-}
+};
 
 const TLS_CIPHER_OPTION = {
     RSA_AES_128_CBC: "TLS_RSA_WITH_AES_128_CBC_SHA",
@@ -92,6 +92,12 @@ const UTLS_FINGERPRINT = {
     UTLS_RANDOMIZED: "randomized",
 };
 
+const ALPN_OPTION = {
+    H2: "h2",
+    HTTP1: "http/1.1",
+    BOTH: "h2,http/1.1",
+};
+
 Object.freeze(Protocols);
 Object.freeze(VmessMethods);
 Object.freeze(SSMethods);
@@ -102,6 +108,7 @@ Object.freeze(TLS_FLOW_CONTROL);
 Object.freeze(TLS_VERSION_OPTION);
 Object.freeze(TLS_CIPHER_OPTION);
 Object.freeze(UTLS_FINGERPRINT);
+Object.freeze(ALPN_OPTION);
 
 class XrayCommonClass {
 
@@ -471,7 +478,7 @@ class GrpcStreamSettings extends XrayCommonClass {
 class TlsStreamSettings extends XrayCommonClass {
     constructor(serverName = '', minVersion = TLS_VERSION_OPTION.TLS12, maxVersion = TLS_VERSION_OPTION.TLS13,
         cipherSuites = '',
-        certificates = [new TlsStreamSettings.Cert()], alpn = ["h2", "http/1.1"]) {
+        certificates = [new TlsStreamSettings.Cert()], alpn=[''] ,settings=[new TlsStreamSettings.Settings()]) {
         super();
         this.server = serverName;
         this.minVersion = minVersion;
@@ -479,6 +486,7 @@ class TlsStreamSettings extends XrayCommonClass {
         this.cipherSuites = cipherSuites;
         this.certs = certificates;
         this.alpn = alpn;
+		this.settings = settings;
     }
 
     addCert(cert) {
@@ -491,9 +499,14 @@ class TlsStreamSettings extends XrayCommonClass {
 
     static fromJson(json={}) {
         let certs;
+		let settings;
         if (!ObjectUtil.isEmpty(json.certificates)) {
             certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert));
         }
+		if (!ObjectUtil.isEmpty(json.settings)) {
+            let values = json.settings[0];
+            settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
+        }
 
         return new TlsStreamSettings(
             json.serverName,
@@ -502,6 +515,7 @@ class TlsStreamSettings extends XrayCommonClass {
             json.cipherSuites,
             certs,
             json.alpn,
+			settings,
         );
     }
 
@@ -512,7 +526,9 @@ class TlsStreamSettings extends XrayCommonClass {
             maxVersion: this.maxVersion,
             cipherSuites: this.cipherSuites,
             certificates: TlsStreamSettings.toJsonArray(this.certs),
-            alpn: this.alpn
+            alpn: this.alpn,
+            settings: TlsStreamSettings.toJsonArray(this.settings),
+			
         };
     }
 }
@@ -558,6 +574,30 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
     }
 };
 
+TlsStreamSettings.Settings = class extends XrayCommonClass {
+  constructor(insecure = false, fingerprint = '', serverName = '') {
+    super();
+    this.inSecure = insecure;
+    this.fingerprint = fingerprint;
+    this.serverName = serverName;
+  }
+  static fromJson(json = {}) {
+    return new TlsStreamSettings.Settings(
+      json.allowInsecure,
+      json.fingerprint,
+      json.servername,
+    );
+  }
+  toJson() {
+    return {
+      allowInsecure: this.inSecure,
+      fingerprint: this.fingerprint,
+      serverName: this.serverName,
+    };
+  }
+};
+
+
 class StreamSettings extends XrayCommonClass {
     constructor(network='tcp',
 		security='none',
@@ -593,12 +633,12 @@ class StreamSettings extends XrayCommonClass {
         }
     }
 
-    get isXTls() {
+    get isXTLS() {
         return this.security === "xtls";
     }
 
-    set isXTls(isXTls) {
-        if (isXTls) {
+    set isXTLS(isXTLS) {
+        if (isXTLS) {
             this.security = 'xtls';
         } else {
             this.security = 'none';
@@ -608,7 +648,7 @@ class StreamSettings extends XrayCommonClass {
     static fromJson(json={}) {
         let tls;
         if (json.security === "xtls") {
-            tls = TlsStreamSettings.fromJson(json.xtlsSettings);
+            tls = TlsStreamSettings.fromJson(json.XTLSSettings);
         } else {
             tls = TlsStreamSettings.fromJson(json.tlsSettings);
         }
@@ -631,7 +671,7 @@ class StreamSettings extends XrayCommonClass {
             network: network,
             security: this.security,
             tlsSettings: this.isTls ? this.tls.toJson() : undefined,
-            xtlsSettings: this.isXTls ? this.tls.toJson() : undefined,
+            XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined,
             tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
             kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
             wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
@@ -666,7 +706,7 @@ class Sniffing extends XrayCommonClass {
 class Inbound extends XrayCommonClass {
     constructor(port=RandomUtil.randomIntRange(10000, 60000),
                 listen='',
-                protocol=Protocols.VMESS,
+                protocol=Protocols.VLESS,
                 settings=null,
                 streamSettings=new StreamSettings(),
                 tag='',
@@ -710,12 +750,12 @@ class Inbound extends XrayCommonClass {
         }
     }
 
-    get xtls() {
+    get XTLS() {
         return this.stream.security === 'xtls';
     }
 
-    set xtls(isXTls) {
-        if (isXTls) {
+    set XTLS(isXTLS) {
+        if (isXTLS) {
             this.stream.security = 'xtls';
         } else {
             this.stream.security = 'none';
@@ -837,7 +877,7 @@ class Inbound extends XrayCommonClass {
     }
 
     get serverName() {
-        if (this.stream.isTls || this.stream.isXTls) {
+        if (this.stream.isTls || this.stream.isXTLS) {
             return this.stream.tls.server;
         }
         return "";
@@ -931,7 +971,7 @@ class Inbound extends XrayCommonClass {
         }
     }
 	
-    //this is used for xtls-rprx-vison
+    //this is used for xtls-rprx-vision
     canEnableTlsFlow() {
         if ((this.stream.security === 'tls') && (this.network === "tcp")) {
             switch (this.protocol) {
@@ -947,8 +987,9 @@ class Inbound extends XrayCommonClass {
     canSetTls() {
         return this.canEnableTls();
     }
+     
 
-    canEnableXTls() {
+    canEnableXTLS() {
         switch (this.protocol) {
             case Protocols.VLESS:
             case Protocols.TROJAN:
@@ -1053,6 +1094,9 @@ class Inbound extends XrayCommonClass {
             host: host,
             path: path,
             tls: this.stream.security,
+			sni: this.stream.tls.settings[0]['serverName'],
+            fp: this.stream.tls.settings[0]['fingerprint'],
+            alpn: this.stream.tls.alpn[0],
         };
         return 'vmess://' + base64(JSON.stringify(obj, null, 2));
     }
@@ -1064,7 +1108,8 @@ class Inbound extends XrayCommonClass {
         const type = this.stream.network;
         const params = new Map();
         params.set("type", this.stream.network);
-        if (this.xtls) {
+		params.set("security", this.stream.security);
+        if (this.XTLS) {
             params.set("security", "xtls");
         } else {
             params.set("security", this.stream.security);
@@ -1117,16 +1162,33 @@ class Inbound extends XrayCommonClass {
         if (this.stream.security === 'tls') {
             if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
                 address = this.stream.tls.server;
-                params.set("sni", address);
-            }
-			if (this.settings.vlesses[clientIndex].flow === "xtls-rprx-vision") {
-                params.set("flow", this.settings.vlesses[clientIndex].flow);
-            }
-            params.set("fp", this.settings.vlesses[clientIndex].fingerprint);
+                params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
+                params.set("alpn", this.stream.tls.alpn[0]);
+                if (this.stream.tls.settings[0]['serverName'] !== ''){
+                    params.set("sni", this.stream.tls.settings[0]['serverName']);
+                }
+                else{
+                   params.set("sni", address);
+                }
+                if (type === "tcp") {
+                    params.set("flow", this.settings.vlesses[clientIndex].flow);
+                }
+			}
         }
-
-        if (this.xtls) {
-            params.set("flow", this.settings.vlesses[clientIndex].flow);
+		
+		if (this.stream.security === 'xtls') {
+            if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
+                address = this.stream.tls.server;
+                if (this.stream.tls.settings[0]['serverName'] !== ''){
+                    params.set("sni", this.stream.tls.settings[0]['serverName']);
+                }
+                else{
+                   params.set("sni", address);
+                }
+                if (type === "tcp") {
+                    params.set("flow", this.settings.vlesses[clientIndex].flow);
+                }
+			}
         }
 
         const link = `vless://${uuid}@${address}:${port}`;
@@ -1138,7 +1200,7 @@ class Inbound extends XrayCommonClass {
         return url.toString();
     }
 
-    genSSLink(address = '', remark = '',clientIndex) {
+    genSSLink(address = '', remark = '') {
         let settings = this.settings;
         const server = this.stream.tls.server;
         if (!ObjectUtil.isEmpty(server)) {
@@ -1157,12 +1219,6 @@ class Inbound extends XrayCommonClass {
         const port = this.port;
         const type = this.stream.network;
         const params = new Map();
-        params.set("type", this.stream.network);
-        if (this.xtls) {
-            params.set("security", "xtls");
-        } else {
-            params.set("security", this.stream.security);
-        }
         switch (type) {
             case "tcp":
                 const tcp = this.stream.tcp;
@@ -1211,13 +1267,32 @@ class Inbound extends XrayCommonClass {
         if (this.stream.security === 'tls') {
             if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
                 address = this.stream.tls.server;
-                params.set("sni", address);
-            }
-            params.set("flow", this.settings.trojans[clientIndex].flow);
+                params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
+                params.set("alpn", this.stream.tls.alpn[0]);
+                if (this.stream.tls.settings[0]['serverName'] !== ''){
+                    params.set("sni", this.stream.tls.settings[0]['serverName']);
+                }
+                else{
+                   params.set("sni", address);
+                }
+			}
         }
-        if (this.xtls) {
-            params.set("flow", this.settings.trojans[clientIndex].flow);
+		
+		if (this.stream.security === 'xtls') {
+            if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
+                address = this.stream.tls.server;
+                if (this.stream.tls.settings[0]['serverName'] !== ''){
+                    params.set("sni", this.stream.tls.settings[0]['serverName']);
+                }
+                else{
+                   params.set("sni", address);
+                }
+                if (type === "tcp") {
+                    params.set("flow", this.settings.trojans[clientIndex].flow);
+                }
+			}
         }
+        
         const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
         const url = new URL(link);
         for (const [key, value] of params) {
@@ -1227,7 +1302,7 @@ class Inbound extends XrayCommonClass {
         return url.toString();
     }
 
-    genLink(address='', remark='') {
+    genLink(address='', remark='', clientIndex=0) {
         switch (this.protocol) {
             case Protocols.VMESS:
                 if (this.settings.vmesses[clientIndex].email != ""){
@@ -1249,7 +1324,7 @@ class Inbound extends XrayCommonClass {
         }
     }
     genInboundLinks(address = '', remark = '') {
-        let link = '';
+    let link = '';
         switch (this.protocol) {
             case Protocols.VMESS:
             case Protocols.VLESS:
@@ -1262,7 +1337,7 @@ class Inbound extends XrayCommonClass {
                 return (this.genSSLink(address, remark) + '\r\n');
             default: return '';
         }
-    }
+}
 
     static fromJson(json={}) {
         return new Inbound(
@@ -1429,7 +1504,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
                 fallbacks=[],) {
         super(protocol);
         this.vlesses = vlesses;
-        this.decryption = decryption;
+        this.decryption = 'none';
         this.fallbacks = fallbacks;
     }
 
@@ -1445,7 +1520,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
         return new Inbound.VLESSSettings(
             Protocols.VLESS,
             json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
-            json.decryption,
+            'none',
             Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),
         );
     }
@@ -1461,14 +1536,13 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
 };
 Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
 
-    constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, fingerprint = UTLS_FINGERPRINT.UTLS_CHROME, expiryTime='') {
+    constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') {
         super();
         this.id = id;
         this.flow = flow;
         this.email = email;
         this.limitIp = limitIp;
         this.totalGB = totalGB;
-        this.fingerprint = fingerprint;
         this.expiryTime = expiryTime;
 
     }
@@ -1480,7 +1554,6 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
             json.email,
             json.limitIp,
             json.totalGB,
-            json.fingerprint,
             json.expiryTime,
 
         );

+ 22 - 0
web/controller/server.go

@@ -34,6 +34,8 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
 	g.Use(a.checkLogin)
 	g.POST("/status", a.status)
 	g.POST("/getXrayVersion", a.getXrayVersion)
+	g.POST("/stopXrayService", a.stopXrayService)
+	g.POST("/restartXrayService", a.restartXrayService)
 	g.POST("/installXray/:version", a.installXray)
 }
 
@@ -83,3 +85,23 @@ func (a *ServerController) installXray(c *gin.Context) {
 	err := a.serverService.UpdateXray(version)
 	jsonMsg(c, I18n(c, "install")+" xray", err)
 }
+
+func (a *ServerController) stopXrayService(c *gin.Context) {
+        a.lastGetStatusTime = time.Now()
+	err := a.serverService.StopXrayService()
+	if err != nil {
+		jsonMsg(c, "", err)
+		return
+	}
+	jsonMsg(c, "Xray stoped",err)
+
+}
+func (a *ServerController) restartXrayService(c *gin.Context) {
+	err := a.serverService.RestartXrayService()
+	if err != nil {
+		jsonMsg(c, "", err)
+		return
+	}
+	jsonMsg(c, "Xray restarted",err)
+
+}

+ 1 - 0
web/html/common/prompt_modal.html

@@ -1,6 +1,7 @@
 {{define "promptModal"}}
 <a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
          :closable="true" @ok="promptModal.ok" :mask-closable="false"
+		 :class="siderDrawer.isDarkTheme ? darkClass : ''"
          :ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
     <a-input id="prompt-modal-input" :type="promptModal.type"
              v-model="promptModal.value"

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

@@ -1,6 +1,7 @@
 {{define "qrcodeModal"}}
 <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
          :closable="true" width="300px" :ok-text="qrModal.okText"
+		 :class="siderDrawer.isDarkTheme ? darkClass : ''"
          cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}">
 		<canvas id="qrCode" style="width: 100%; height: 100%;"></canvas>
 </a-modal>

+ 4 - 10
web/html/common/text_modal.html

@@ -1,9 +1,10 @@
 {{define "textModal"}}
 <a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
          :closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
+		 :class="siderDrawer.isDarkTheme ? darkClass : ''"
          :ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
     <a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
-              @click="downloader.download(txtModal.fileName, txtModal.content)">
+              :href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :download="txtModal.fileName">
         {{ i18n "download" }} [[ txtModal.fileName ]]
     </a-button>
     <a-input type="textarea" v-model="txtModal.content"
@@ -31,15 +32,7 @@
                     });
                     this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
                 }
-                if (this.qrcode === null) {
-                    this.qrcode = new QRious({
-                        element: document.querySelector('#qrCode'),
-                        size: 260,
-                        value: content,
-                    });
-                } else {
-                    this.qrcode.value = content;
-                }
+               
             });
         },
         close: function () {
@@ -48,6 +41,7 @@
     };
 
     const textModalApp = new Vue({
+    	delimiters: ['[[', ']]'],
         el: '#text-modal',
         data: {
             txtModal: txtModal,

+ 39 - 5
web/html/xui/common_sider.html

@@ -24,7 +24,7 @@
         <a-icon type="github"></a-icon>
         <span>Github</span>
     </a-menu-item>
-    <a-menu-item key="https://t.me/xxxuiforever">
+    <a-menu-item key="https://t.me/panel3xui">
         <a-icon type="usergroup-add"></a-icon>
         <span>Telegram</span>
     </a-menu-item>
@@ -37,27 +37,51 @@
 
 
 {{define "commonSider"}}
-<a-layout-sider id="sider" collapsible breakpoint="md" collapsed-width="0">
-    <a-menu theme="dark" mode="inline" :selected-keys="['{{ .request_uri }}']"
+<a-layout-sider :theme="siderDrawer.theme" id="sider" collapsible breakpoint="md" collapsed-width="0">
+    <a-menu :theme="siderDrawer.theme" mode="inline" selected-keys="">
+        <a-menu-item mode="inline">
+            <a-icon type="bg-colors"></a-icon>
+            <a-switch :default-checked="siderDrawer.isDarkTheme"
+            checked-children="☀"
+            un-checked-children="🌙"
+            @change="siderDrawer.changeTheme()"></a-switch>
+        </a-menu-item>
+    </a-menu>
+    <a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']"
             @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
         {{template "menuItems" .}}
     </a-menu>
 </a-layout-sider>
 <a-drawer id="sider-drawer" placement="left" :closable="false"
           @close="siderDrawer.close()"
-          :visible="siderDrawer.visible" :wrap-style="{ padding: 0 }">
+          :visible="siderDrawer.visible"
+          :wrap-style="{ padding: 0 }">
     <div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
         <a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
     </div>
-    <a-menu theme="light" mode="inline" :selected-keys="['{{ .request_uri }}']"
+    <a-menu mode="inline" selected-keys="">
+        <a-menu-item mode="inline">
+            <a-icon type="bg-colors"></a-icon>
+            <a-switch :default-checked="siderDrawer.isDarkTheme"
+            checked-children="☀"
+            un-checked-children="🌙"
+            @change="siderDrawer.changeTheme()"></a-switch>
+        </a-menu-item>
+    </a-menu>
+    <a-menu mode="inline" :selected-keys="['{{ .request_uri }}']"
         @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
         {{template "menuItems" .}}
     </a-menu>
 </a-drawer>
 <script>
 
+
+    const darkClass = "ant-card-dark";
+    const bgDarkStyle = "background-color: #242c3a";
     const siderDrawer = {
         visible: false,
+		collapsed: false,
+        isDarkTheme: localStorage.getItem("dark-mode") === 'true' ? true : false,
         show() {
             this.visible = true;
         },
@@ -66,6 +90,16 @@
         },
         change() {
             this.visible = !this.visible;
+		 },
+        toggleCollapsed() {
+            this.collapsed = !this.collapsed;
+        },
+        changeTheme() {
+            this.isDarkTheme = ! this.isDarkTheme;
+            localStorage.setItem("dark-mode", this.isDarkTheme);
+        },
+        get theme() {
+            return this.isDarkTheme ? 'dark' : 'light';
         }
     };
 

+ 0 - 94
web/html/xui/component/inbound_info.html

@@ -1,94 +0,0 @@
-{{define "inboundInfoStream"}}
-<p>{{ i18n "transmission" }}: <a-tag color="green">[[ inbound.network ]]</a-tag></p>
-
-<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
-    <p v-if="inbound.host">host: <a-tag color="green">[[ inbound.host ]]</a-tag></p>
-    <p v-else>{{ i18n "host" }}: <a-tag color="orange">{{ i18n "none" }}</a-tag></p>
-
-    <p v-if="inbound.path">path: <a-tag color="green">[[ inbound.path ]]</a-tag></p>
-    <p v-else>{{ i18n "path" }}: <a-tag color="orange">{{ i18n "none" }}</a-tag></p>
-</template>
-
-<template v-if="inbound.isQuic">
-    <p>quic {{ i18n "encryption" }}: <a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></p>
-    <p>quic {{ i18n "password" }}: <a-tag color="green">[[ inbound.quicKey ]]</a-tag></p>
-    <p>quic {{ i18n "camouflage" }}: <a-tag color="green">[[ inbound.quicType ]]</a-tag></p>
-</template>
-
-<template v-if="inbound.isKcp">
-    <p>kcp {{ i18n "encryption" }}: <a-tag color="green">[[ inbound.kcpType ]]</a-tag></p>
-    <p>kcp {{ i18n "password" }}: <a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></p>
-</template>
-
-<template v-if="inbound.isGrpc">
-    <p>grpc serviceName: <a-tag color="green">[[ inbound.serviceName ]]</a-tag></p>
-</template>
-
-<template v-if="inbound.tls || inbound.xtls">
-    <p v-if="inbound.tls">tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag></p>
-    <p v-if="inbound.xtls">xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag></p>
-</template>
-<template v-else>
-    <p>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag></p>
-</template>
-<p v-if="inbound.tls">
-    tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
-</p>
-<p v-if="inbound.xtls">
-    xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
-</p>
-{{end}}
-
-
-{{define "component/inboundInfoComponent"}}
-<div>
-    <p>{{ i18n "protocol"}}: <a-tag color="green">[[ dbInbound.protocol ]]</a-tag></p>
-    <p>{{ i18n "pages.inbounds.address"}}: <a-tag color="blue">[[ dbInbound.address ]]</a-tag></p>
-    <p>{{ i18n "pages.inbounds.port"}}: <a-tag color="green">[[ dbInbound.port ]]</a-tag></p>
-
-    <template v-if="dbInbound.isVMess" v-for="(vmess, index) in inbound.settings.vmesses">
-        <p>uuid: <a-tag color="green">[[ vmess.id ]]</a-tag></p>
-        <p>alterId: <a-tag color="green">[[ vmess.alterId ]]</a-tag></p>
-        <a-divider style="height: 2px; background-color: #7e7e7e" />
-    </template>
-
-    <template v-if="dbInbound.isVLess" v-for="(vless, index) in inbound.settings.vlesses">
-        <p>uuid: <a-tag color="green">[[ vless.id ]]</a-tag></p>
-        <p v-if="inbound.isXTls">flow: <a-tag color="green">[[ vless.flow ]]</a-tag></p>
-        <a-divider style="height: 2px; background-color: #7e7e7e" />
-    </template>
-
-    <template v-if="dbInbound.isTrojan">
-        <p>{{ i18n "password"}}: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
-    </template>
-
-    <template v-if="dbInbound.isSS">
-        <p>{{ i18n "encryption"}}: <a-tag color="green">[[ inbound.method ]]</a-tag></p>
-        <p>{{ i18n "password"}}: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
-    </template>
-
-    <template v-if="dbInbound.isSocks">
-        <p>{{ i18n "username"}}: <a-tag color="green">[[ inbound.username ]]</a-tag></p>
-        <p>{{ i18n "password"}}: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
-    </template>
-
-    <template v-if="dbInbound.isHTTP">
-        <p>{{ i18n "username"}}: <a-tag color="green">[[ inbound.username ]]</a-tag></p>
-        <p>{{ i18n "password"}}: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
-    </template>
-
-    <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
-        {{template "inboundInfoStream"}}
-    </template>
-</div>
-{{end}}
-
-{{define "component/inboundInfo"}}
-<script>
-    Vue.component('inbound-info', {
-        delimiters: ['[[', ']]'],
-        props: ["dbInbound", "inbound"],
-        template: `{{template "component/inboundInfoComponent"}}`,
-    });
-</script>
-{{end}}

+ 45 - 43
web/html/xui/form/protocol/trojan.html

@@ -1,6 +1,6 @@
 {{define "form/trojan"}}
 <a-form layout="inline">
-<label>{{ i18n "clients"}} </label>
+<label style="color: green;">{{ i18n "clients"}}</label>
 <a-collapse activeKey="0"  v-for="(trojan, index) in inbound.settings.trojans"
 :key="`trojan-${index}`">
 
@@ -20,8 +20,11 @@
                             xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="anticon anticon-question-circle" viewBox="0 0 16 16"> <path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/> <path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/> </svg>
                     </a-tooltip>
                 </span>
-                <a-input v-model.trim="trojan.email"></a-input>
+                <a-input v-model.trim="trojan.email" style="width: 150px;"></a-input>
             </a-form-item>
+			<a-form-item label="Password" >
+				<a-input v-model.trim="trojan.password" style="width: 150px;"></a-input>
+			</a-form-item>
 			<a-form-item>
                 <span slot="label">
                     IP Count Limit
@@ -32,7 +35,7 @@
                         <a-icon type="question-circle" theme="filled"></a-icon>
                     </a-tooltip>
                 </span>
-                <a-input type="number" v-model.number="trojan.limitIp" min="0" ></a-input>
+                <a-input type="number" v-model.number="trojan.limitIp" min="0" style="width: 70px;"></a-input>
             </a-form-item>
             <a-form-item v-if="trojan.email && trojan.limitIp > 0 && isEdit">
                 <span slot="label">
@@ -53,15 +56,12 @@
                     </a-tooltip>
                 </span>
                 <a-form layout="block">
-                    <a-textarea readonly @click="getDBClientIps(trojan.email,$event)" placeholder="Click To Get IPs"  :auto-size="{ minRows: 0.5, maxRows: 10 }">
+                    <a-textarea readonly @click="getDBClientIps(trojan.email,$event)" placeholder="Click To Get IPs"  :auto-size="{ minRows: 2, maxRows: 10 }">
                     </a-textarea>
                 </a-form>
             </a-form-item>
         </a-form>
-        <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="trojan.flow" style="width: 150px">
                 <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>
@@ -90,7 +90,7 @@
                 </a-tooltip>
             </span>
             <a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
-                           v-model="trojan._expiryTime" style="width: 300px;"></a-date-picker>
+                           v-model="trojan._expiryTime" style="width: 170px;"></a-date-picker>
         </a-form-item>
         <a-form layout="inline">
             <a-tooltip v-if="trojan._totalGB > 0">
@@ -123,39 +123,41 @@
     </svg>
 </a-tag>
 
-<a-form layout="inline">
-    <a-form-item label="Fallbacks">
-        <a-row>
-            <a-button type="primary" size="small"
-                      @click="inbound.settings.addTrojanFallback()">
-                +
-            </a-button>
-        </a-row>
-    </a-form-item>
-</a-form>
+<template v-if="inbound.isTcp && inbound.tls">
+    <a-form layout="inline">
+        <a-form-item label="Fallbacks">
+            <a-row>
+                <a-button type="primary" size="small"
+                        @click="inbound.settings.addTrojanFallback()">
+                    +
+                </a-button>
+            </a-row>
+        </a-form-item>
+    </a-form>
 
-<!-- trojan fallbacks -->
-<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
-    <a-divider>
-        fallback[[ index + 1 ]]
-        <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-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 type="number" v-model.number="fallback.xver"></a-input>
-    </a-form-item>
-    <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
-</a-form>
+ <!-- trojan fallbacks -->
+    <a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
+        <a-divider>
+            fallback[[ index + 1 ]]
+            <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-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 type="number" v-model.number="fallback.xver"></a-input>
+        </a-form-item>
+        <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
+    </a-form>
+</template>
 {{end}}

+ 43 - 46
web/html/xui/form/protocol/vless.html

@@ -1,6 +1,6 @@
 {{define "form/vless"}}
 <a-form layout="inline">
-<label>{{ i18n "clients"}}</label>
+<label style="color: green;">{{ i18n "clients"}}</label>
 <a-collapse activeKey="0"  v-for="(vless, index) in inbound.settings.vlesses"
 :key="`vless-${index}`">
 
@@ -21,8 +21,11 @@
                             xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="anticon anticon-question-circle" viewBox="0 0 16 16"> <path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/> <path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/> </svg>
                     </a-tooltip>
                 </span>
-                <a-input v-model.trim="vless.email"></a-input>
+                <a-input v-model.trim="vless.email" style="width: 150px;"></a-input>
             </a-form-item>
+			<a-form-item label="ID">
+				<a-input v-model.trim="vless.id" style="width: 300px;" ></a-input>
+			</a-form-item>
 			<a-form-item>
                 <span slot="label">
                     IP Count Limit
@@ -33,7 +36,7 @@
                         <a-icon type="question-circle" theme="filled"></a-icon>
                     </a-tooltip>
                 </span>
-                <a-input type="number" v-model.number="vless.limitIp" min="0" ></a-input>
+                <a-input type="number" v-model.number="vless.limitIp" min="0" style="width: 70px;"></a-input>
             </a-form-item>
             <a-form-item v-if="vless.email && vless.limitIp > 0 && isEdit">
                 <span slot="label">
@@ -55,15 +58,12 @@
                 </span>
                 <a-form layout="block">
 
-                    <a-textarea readonly @click="getDBClientIps(vless.email,$event)" placeholder="Click To Get IPs"  :auto-size="{ minRows: 0.5, maxRows: 10 }">
+                    <a-textarea readonly @click="getDBClientIps(vless.email,$event)" placeholder="Click To Get IPs"  :auto-size="{ minRows: 2, maxRows: 10 }">
                     </a-textarea>
                 </a-form>
             </a-form-item>
         </a-form>
-        <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">
+		<a-form-item v-if="inbound.XTLS" label="Flow">
             <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px">
                 <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>
@@ -75,11 +75,6 @@
                 <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
             </a-select>
         </a-form-item>
-        <a-form-item v-if="inbound.tls" label="uTLS" layout="inline">
-                <a-select v-model="inbound.settings.vlesses[index].fingerprint" label="uTLS" style="width: 150px">
-                    <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
-                </a-select>
-        </a-form-item>
         <a-form-item>
             <span slot="label">
                 <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
@@ -137,39 +132,41 @@
     </svg>
 </a-tag>
 
-<a-form layout="inline">
-    <a-form-item label="Fallbacks">
-        <a-row>
-            <a-button type="primary" size="small"
-                      @click="inbound.settings.addFallback()">
-                +
-            </a-button>
-        </a-row>
-    </a-form-item>
-</a-form>
+<template v-if="inbound.isTcp && inbound.tls">
+    <a-form layout="inline">
+        <a-form-item label="Fallbacks">
+            <a-row>
+                <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-divider>
-        fallback[[ index + 1 ]]
-        <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-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 type="number" v-model.number="fallback.xver"></a-input>
-    </a-form-item>
-    <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
-</a-form>
+    <a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
+        <a-divider>
+            fallback[[ index + 1 ]]
+            <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-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 type="number" v-model.number="fallback.xver"></a-input>
+        </a-form-item>
+        <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
+    </a-form>
+</template>
 {{end}}

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

@@ -1,6 +1,6 @@
 {{define "form/vmess"}}
 <a-form layout="inline">
-<label>{{ i18n "clients"}}</label>
+<label style="color: green;">{{ i18n "clients"}}</label>
 <a-collapse activeKey="0"  v-for="(vmess, index) in inbound.settings.vmesses"
 :key="`vmess-${index}`">
     <a-collapse-panel :class="getHeaderStyle(vmess.email)" :header="getHeaderText(vmess.email)">
@@ -20,8 +20,14 @@
                             xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="anticon anticon-question-circle" viewBox="0 0 16 16"> <path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/> <path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/> </svg>
                     </a-tooltip>
                 </span>
-                <a-input v-model.trim="vmess.email"></a-input>
+                <a-input v-model.trim="vmess.email" style="width: 150px;"></a-input>
             </a-form-item>
+			<a-form-item label="ID">
+				<a-input v-model.trim="vmess.id" style="width: 300px;" ></a-input>
+			</a-form-item>
+			<a-form-item label='{{ i18n "additional" }} ID'>
+				<a-input type="number" v-model.number="vmess.alterId"></a-input>
+			</a-form-item>
 			<a-form-item>
                 <span slot="label">
                     IP Count Limit
@@ -32,7 +38,7 @@
                         <a-icon type="question-circle" theme="filled"></a-icon>
                     </a-tooltip>
                 </span>
-              <a-input type="number" v-model.number="vmess.limitIp" min="0" ></a-input>
+              <a-input type="number" v-model.number="vmess.limitIp" min="0" style="width: 70px;" ></a-input>
             </a-form-item>
             <a-form-item v-if="vmess.email && vmess.limitIp > 0 && isEdit">
                 <span slot="label">
@@ -52,16 +58,10 @@
                         </span>
                     </a-tooltip>
                 </span>
-                <a-textarea readonly @click="getDBClientIps(vmess.email,$event)" placeholder="Click To Get IPs"  :auto-size="{ minRows: 0.5, maxRows: 10 }">
+                <a-textarea readonly @click="getDBClientIps(vmess.email,$event)" placeholder="Click To Get IPs"  :auto-size="{ minRows: 2, maxRows: 10 }">
                 </a-textarea>
             </a-form-item>
         </a-form>
-        <a-form-item label="ID">
-            <a-input v-model.trim="vmess.id"></a-input>
-        </a-form-item>
-        <a-form-item label='{{ i18n "additional" }} ID'>
-            <a-input type="number" v-model.number="vmess.alterId"></a-input>
-        </a-form-item>
         <a-form-item>
             <span slot="label">
                 <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)

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

@@ -8,7 +8,7 @@
             <a-select-option value="ws">WS</a-select-option>
             <a-select-option value="http">HTTP</a-select-option>
             <a-select-option value="quic">QUIC</a-select-option>
-            <a-select-option value="grpc">GRPC</a-select-option>
+            <a-select-option value="grpc">gRPC</a-select-option>
         </a-select>
     </a-form-item>
 </a-form>

+ 27 - 15
web/html/xui/form/tls_settings.html

@@ -5,13 +5,22 @@
         <a-switch v-model="inbound.tls">
         </a-switch>
     </a-form-item>
-    <a-form-item v-if="inbound.canEnableXTls()" label="XTLS">
-        <a-switch v-model="inbound.xtls"></a-switch>
+    <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 v-if="inbound.tls || inbound.XTLS"layout="inline">
+    <a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls">
+        <a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input>
+    </a-form-item>
+    <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>
+        </a-select>
+    </a-form-item>
     <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>
@@ -22,17 +31,20 @@
             <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-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>
-        </a-select>
-    </a-form-item>
+        <a-form-item label="uTLS" v-if="inbound.tls" >
+            <a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px">
+                <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='{{ i18n "domainName" }}'>
         <a-input v-model.trim="inbound.stream.tls.server"></a-input>
     </a-form-item>
-    <a-form-item label="Alpn">
-        <a-input v-model.trim="inbound.stream.tls.alpn"></a-input>
+    <a-form-item label="Alpn" placeholder="http/1.1,h2" v-if="inbound.tls">
+        <a-select v-model="inbound.stream.tls.alpn[0]" style="width:200px">
+            <a-select-option value=''>auto</a-select-option>
+            <a-select-option v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-select-option>
+        </a-select>
     </a-form-item>
     <a-form-item label='{{ i18n "certificate" }}'>
         <a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
@@ -42,18 +54,18 @@
     </a-form-item>
     <template v-if="inbound.stream.tls.certs[0].useFile">
         <a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
-            <a-input v-model.trim="inbound.stream.tls.certs[0].certFile"></a-input>
+            <a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:300px;"></a-input>
         </a-form-item>
         <a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
-            <a-input v-model.trim="inbound.stream.tls.certs[0].keyFile"></a-input>
+            <a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input>
         </a-form-item>
     </template>
     <template v-else>
         <a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
-            <a-input type="textarea" :rows="2" v-model="inbound.stream.tls.certs[0].cert"></a-input>
+            <a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
         </a-form-item>
         <a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
-            <a-input type="textarea" :rows="2" v-model="inbound.stream.tls.certs[0].key"></a-input>
+            <a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
         </a-form-item>
     </template>
 </a-form>

+ 1 - 0
web/html/xui/inbound_info_modal.html

@@ -3,6 +3,7 @@
     v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
     :closable="true"
     :mask-closable="true"
+	:class="siderDrawer.isDarkTheme ? darkClass : ''"
     :footer="null"
     width="600px"
     >

+ 26 - 30
web/html/xui/inbound_modal.html

@@ -1,6 +1,7 @@
 {{define "inboundModal"}}
 <a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
          :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
+		 :class="siderDrawer.isDarkTheme ? darkClass : ''"
          :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
     {{template "form/inbound"}}
 </a-modal>
@@ -88,22 +89,18 @@
             removeClient(index, clients) {
                 clients.splice(index, 1);
             },
-            async getDBClientIps(email,event) {
-
-                const msg = await HttpUtil.post('/xui/inbound/clientIps/'+ email);
-                if (!msg.success) {
-                    return;
-                }
-                try {
-                    ips = JSON.parse(msg.obj)
-                    ips = ips.join(",")
-                    event.target.value = ips
-                } catch (error) {
-                    // text
-                    event.target.value = msg.obj
-
-                }
-
+            async getDBClientIps(email, event) {
+              const msg = await HttpUtil.post('/xui/inbound/clientIps/' + email);
+              if (!msg.success) {
+                return;
+              }
+              try {
+                let ips = JSON.parse(msg.obj);
+                ips = ips.join(",");
+                event.target.value = ips;
+              } catch (error) {
+                event.target.value = msg.obj;
+              }
             },
             async clearDBClientIps(email,event) {
                 const msg = await HttpUtil.post('/xui/inbound/clearClientIps/'+ email);
@@ -112,20 +109,19 @@
                 }
                 event.target.value = ""
             },
-            async resetClientTraffic(client,event) {
-                const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
-                if (!msg.success) {
-                    return;
-                }
-                clientStats = this.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
-                            }
+            async resetClientTraffic(client, event) {
+              const msg = await HttpUtil.post(`/xui/inbound/resetClientTraffic/${client.email}`);
+              if (!msg.success) {
+                return;
+              }
+              const clientStats = this.inbound.clientStats;
+              if (clientStats.length > 0) {
+				for (let i = 0; i < clientStats.length; i++) {
+                if (clientStats[i].email === client.email) {
+                clientStats[i].up = 0;
+                clientStats[i].down = 0;
+                break; // Stop looping once we've found the matching client.
+                            
                         }
                     }
                 }

+ 76 - 68
web/html/xui/inbounds.html

@@ -11,11 +11,15 @@
     .ant-col-sm-24 {
         margin-top: 10px;
     }
+
+    .ant-table-row-expand-icon {
+        color: rgba(0,0,0,.65);
+    }
 </style>
 <body>
 <a-layout id="app" v-cloak>
     {{ template "commonSider" . }}
-    <a-layout id="content-layout">
+    <a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
         <a-layout-content>
             <a-spin :spinning="spinning" :delay="500" tip="loading">
                 <transition name="list" appear>
@@ -24,7 +28,7 @@
                     </a-tag>
                 </transition>
                 <transition name="list" appear>
-                    <a-card hoverable style="margin-bottom: 20px;">
+                    <a-card hoverable style="margin-bottom: 20px;" :class="siderDrawer.isDarkTheme ? darkClass : ''">
                         <a-row>
                             <a-col :xs="24" :sm="24" :lg="12">
                                 {{ i18n "pages.inbounds.totalDownUp" }}:
@@ -38,11 +42,17 @@
                                 {{ i18n "pages.inbounds.inboundCount" }}:
                                 <a-tag color="green">[[ dbInbounds.length ]]</a-tag>
                             </a-col>
+                            <a-col :xs="24" :sm="24" :lg="12">
+                                {{ i18n "clients" }}:
+                                <a-tag color="green">[[ total.clients ]]</a-tag>
+                                <a-tag color="blue">{{ i18n "enabled" }} [[ total.active ]]</a-tag>
+                                <a-tag color="red">{{ i18n "disabled" }} [[ total.deactive ]]</a-tag>
+                            </a-col>
                         </a-row>
                     </a-card>
                 </transition>
                 <transition name="list" appear>
-                    <a-card hoverable>
+                    <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                         <div slot="title">
                              <a-button type="primary" @click="openAddInbound">Add Inbound</a-button>
 							 <a-button type="primary" @click="exportAllLinks" class="copy-btn">Export Links</a-button>
@@ -67,6 +77,12 @@
                                             <a-icon type="edit"></a-icon>
                                             {{ i18n "edit" }}
                                         </a-menu-item>
+                                         <template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess">
+                                            <a-menu-item key="export">
+                                                <a-icon type="export"></a-icon>
+                                                {{ i18n "pages.inbounds.export"}}
+                                            </a-menu-item>
+                                        </template>
                                         <a-menu-item key="resetTraffic">
                                             <a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
                                         </a-menu-item>
@@ -202,7 +218,7 @@
         { title: '{{ i18n "pages.inbounds.client" }}', width: 60, scopedSlots: { customRender: 'client' } },
         { title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 100, scopedSlots: { customRender: 'traffic' } },
         { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
-        { title: 'UID', width: 120, dataIndex: "id" },
+        { title: 'UID', width: 150, dataIndex: "id" },
 		
     ];
 
@@ -281,6 +297,9 @@
                     case "qrcode":
                         this.showQrcode(dbInbound);
                         break;
+                    case "export":
+                        this.inboundLinks(dbInbound.id);
+                        break;
                     case "edit":
                         this.openEditInbound(dbInbound.id);
                         break;
@@ -372,18 +391,6 @@
                     },
                 });
             },
-			exportAllLinks() {
-                    let copyText = '';
-                    for (const dbInbound of this.dbInbounds) {
-                        copyText += dbInbound.genInboundLinks
-                    }
-                    const clipboard = new ClipboardJS('.copy-btn', {
-                        text: function () {
-                            return copyText;
-                        }
-                    });
-                    clipboard.on('success', () => { this.$message.success('Export Links succeed'); });
-                },
             delInbound(dbInbound) {
                 this.$confirm({
                     title: '{{ i18n "pages.inbounds.deleteInbound"}}',
@@ -393,7 +400,15 @@
                     onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
                 });
             },
-            showQrcode(dbInbound, clientIndex) {
+			getClients(protocol, clientSettings) {
+                switch(protocol){
+                    case Protocols.VMESS: return clientSettings.vmesses;
+                    case Protocols.VLESS: return clientSettings.vlesses;
+                    case Protocols.TROJAN: return clientSettings.trojans;
+                    default: return null;
+                }
+            },
+             showQrcode(dbInbound, clientIndex) {
                 const link = dbInbound.genLink(clientIndex);
                 qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
             },
@@ -451,60 +466,34 @@
                 return dbInbound.toInbound().isExpiry(index)
             },
             getUpStats(dbInbound, email) {
-                clientStats = dbInbound.clientStats
-                if(clientStats.length > 0)
-                {
-                    for (const key in clientStats) {
-                        if (Object.hasOwnProperty.call(clientStats, key)) {
-                            if(clientStats[key]['email'] == email)
-                                return clientStats[key]['up']
-
-                        }
-                    }
-                }
-
+                if(email.length == 0) return 0
+                clientStats = dbInbound.clientStats.find(stats => stats.email === email)
+                return clientStats ? clientStats.up : 0
             },
             getDownStats(dbInbound, email) {
-                clientStats = dbInbound.clientStats
-                if(clientStats.length > 0)
-                {
-                    for (const key in clientStats) {
-                        if (Object.hasOwnProperty.call(clientStats, key)) {
-                            if(clientStats[key]['email'] == email)
-                                return clientStats[key]['down']
-
-                        }
-                    }
-                }
+                if(email.length == 0) return 0
+                clientStats = dbInbound.clientStats.find(stats => stats.email === email)
+                return clientStats ? clientStats.down : 0
             },
             isTrafficExhausted(dbInbound, email) {
-                clientStats = dbInbound.clientStats
-                if(clientStats.length > 0)
-                {
-                    for (const key in clientStats) {
-                        if (Object.hasOwnProperty.call(clientStats, key)) {
-                            if(clientStats[key]['email'] == email)
-                                return clientStats[key]['down']+clientStats[key]['up'] > clientStats[key]['total']
-
-                        }
-                    }
+                if(email.length == 0) return false
+                clientStats = dbInbound.clientStats.find(stats => stats.email === email)
+                return clientStats ? clientStats.down + clientStats.up > clientStats.total : false
+            },
+            inboundLinks(dbInboundId) {
+                dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
+                txtModal.show('{{ i18n "pages.inbounds.export"}}',dbInbound.genInboundLinks,dbInbound.remark);
+            },
+			exportAllLinks() {
+                let copyText = '';
+                for (const dbInbound of this.dbInbounds) {
+                    copyText += dbInbound.genInboundLinks
                 }
+                txtModal.show('{{ i18n "pages.inbounds.export"}}',copyText,'All-Inbounds');
             },
             isClientEnabled(dbInbound, email) {
-                clientStats = dbInbound.clientStats
-                if(clientStats.length > 0)
-                {
-                    for (const key in clientStats) {
-                        if (Object.hasOwnProperty.call(clientStats, key)) {
-                            if(clientStats[key]['email'] == email)
-                                return clientStats[key]['enable']
-
-                        }
-                    }
-                }
-                 else{
-                    return true
-                }
+                clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
+                return clientStats ? clientStats['enable'] : true
             },
         },
         watch: {
@@ -518,13 +507,32 @@
         computed: {
             total() {
                 let down = 0, up = 0;
-                for (let i = 0; i < this.dbInbounds.length; ++i) {
-                    down += this.dbInbounds[i].down;
-                    up += this.dbInbounds[i].up;
-                }
+				let clients = 0, active = 0, deactive = 0;
+                this.dbInbounds.forEach(dbInbound => {
+                    down += dbInbound.down;
+                    up += dbInbound.up;
+                    inbound = dbInbound.toInbound();
+                    clients = this.getClients(dbInbound.protocol, inbound.settings);
+                    if(clients){
+                        if(dbInbound.enable){
+                            isClientEnable = false;
+                            clients.forEach(client => {
+                                isClientEnable = client.email == "" ? true: this.isClientEnabled(dbInbound,client.email);
+                                isClientEnable ? active++ : deactive++;
+                            });
+                        } else {
+                            deactive += clients.length;
+                        }
+                    } else {
+                        dbInbound.enable ? active++ : deactive++;
+                    }
+                });
                 return {
                     down: down,
                     up: up,
+                    clients: active + deactive,
+                    active: active,
+                    deactive: deactive,
                 };
             }
         },

+ 33 - 9
web/html/xui/index.html

@@ -15,24 +15,26 @@
 <body>
 <a-layout id="app" v-cloak>
     {{ template "commonSider" . }}
-    <a-layout id="content-layout">
+    <a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
         <a-layout-content>
             <a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
             <transition name="list" appear>
                 <a-row>
-                    <a-card hoverable>
+                    <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                         <a-row>
                             <a-col :sm="24" :md="12">
                                 <a-row>
                                     <a-col :span="12" style="text-align: center">
                                         <a-progress type="dashboard" status="normal"
                                                     :stroke-color="status.cpu.color"
+													:class="siderDrawer.isDarkTheme ? darkClass : ''"
                                                     :percent="status.cpu.percent"></a-progress>
                                         <div>CPU</div>
                                     </a-col>
                                     <a-col :span="12" style="text-align: center">
                                         <a-progress type="dashboard" status="normal"
                                                     :stroke-color="status.mem.color"
+													:class="siderDrawer.isDarkTheme ? darkClass : ''"
                                                     :percent="status.mem.percent"></a-progress>
                                         <div>
                                             {{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
@@ -45,6 +47,7 @@
                                     <a-col :span="12" style="text-align: center">
                                         <a-progress type="dashboard" status="normal"
                                                     :stroke-color="status.swap.color"
+													:class="siderDrawer.isDarkTheme ? darkClass : ''"
                                                     :percent="status.swap.percent"></a-progress>
                                         <div>
                                             Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
@@ -53,6 +56,7 @@
                                     <a-col :span="12" style="text-align: center">
                                         <a-progress type="dashboard" status="normal"
                                                     :stroke-color="status.disk.color"
+													:class="siderDrawer.isDarkTheme ? darkClass : ''"
                                                     :percent="status.disk.percent"></a-progress>
                                         <div>
                                             {{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
@@ -67,7 +71,7 @@
             <transition name="list" appear>
                 <a-row>
                     <a-col :sm="24" :md="12">
-                        <a-card hoverable>
+                        <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                             {{ i18n "pages.index.xrayStatus" }}:
                             <a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
                             <a-tooltip v-if="status.xray.state === State.Error">
@@ -77,11 +81,13 @@
                                 <a-icon type="question-circle" theme="filled"></a-icon>
                             </a-tooltip>
                             <a-tag color="green" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
-                            <a-tag color="blue" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch"}}</a-tag>
+                            <a-tag color="blue" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
+                            <a-tag color="blue" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>                    
+                            <a-tag color="blue" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
                         </a-card>
                     </a-col>
                     <a-col :sm="24" :md="12">
-                        <a-card hoverable>
+                        <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                             {{ i18n "pages.index.operationHours" }}:
                             <a-tag color="#87d068">[[ formatSecond(status.uptime) ]]</a-tag>
                             <a-tooltip>
@@ -93,12 +99,12 @@
                         </a-card>
                     </a-col>
                     <a-col :sm="24" :md="12">
-                        <a-card hoverable>
+                        <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                             {{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
                         </a-card>
                     </a-col>
                     <a-col :sm="24" :md="12">
-                        <a-card hoverable>
+                        <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                             TCP / UDP {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
                             <a-tooltip>
                                 <template slot="title">
@@ -109,7 +115,7 @@
                         </a-card>
                     </a-col>
                     <a-col :sm="24" :md="12">
-                        <a-card hoverable>
+                        <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                             <a-row>
                                 <a-col :span="12">
                                     <a-icon type="arrow-up"></a-icon>
@@ -135,7 +141,7 @@
                         </a-card>
                     </a-col>
                     <a-col :sm="24" :md="12">
-                        <a-card hoverable>
+                        <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                             <a-row>
                                 <a-col :span="12">
                                     <a-icon type="cloud-upload"></a-icon>
@@ -315,6 +321,24 @@
                         this.loading(false);
                     },
                 });
+            },
+			//here add stop xray function
+            async stopXrayService() {
+                this.loading(true);
+                const msg = await HttpUtil.post('server/stopXrayService');
+                this.loading(false);
+                if (!msg.success) {
+                    return;
+                }
+            },
+        //here add restart xray function
+            async restartXrayService() {
+                this.loading(true);
+                const msg = await HttpUtil.post('server/restartXrayService');
+                this.loading(false);
+                if (!msg.success) {
+                    return;
+                }
             },
         },
         async mounted() {

+ 10 - 10
web/html/xui/setting.html

@@ -20,14 +20,14 @@
         display: block;
     }
 
-    .ant-tabs-top-bar {
+	:not(.ant-card-dark)>.ant-tabs-top-bar {
         background: white;
     }
 </style>
 <body>
 <a-layout id="app" v-cloak>
     {{ template "commonSider" . }}
-    <a-layout id="content-layout">
+    <a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
         <a-layout-content>
             <a-spin :spinning="spinning" :delay="500" tip="loading">
                 <a-space direction="vertical">
@@ -35,17 +35,17 @@
                         <a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button>
                         <a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button>
                     </a-space>
-                    <a-tabs default-active-key="1">
+                    <a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
                         <a-tab-pane key="1" tab='{{ i18n "pages.setting.panelConfig"}}'>
 
-                            <a-list item-layout="horizontal" style="background: white">
+                            <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
                                 <setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
                                 <a-list-item>
-                                    <a-row  style="padding: 20px">
+                                    <a-row style="padding: 20px">
                                         <a-col :lg="24" :xl="12">
                                             <a-list-item-meta title="Language"/>
                                         </a-col>
@@ -58,7 +58,7 @@
                                                         @change="setLang(lang)"
                                                         style="width: 100%"
                                                 >
-                                                    <a-select-option  :value="l.value" label="China" v-for="l in supportLangs" >
+                                                    <a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
                                                         <span role="img" aria-label="l.name" v-text="l.icon"></span>
                                                         &nbsp;&nbsp;<span v-text="l.name"></span>
                                                     </a-select-option>
@@ -71,7 +71,7 @@
                             </a-list>
                         </a-tab-pane>
                         <a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'>
-                            <a-form style="background: white; padding: 20px">
+                            <a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
                                 <a-form-item label='{{ i18n "pages.setting.oldUsername"}}'>
                                     <a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
                                 </a-form-item>
@@ -93,12 +93,12 @@
                             </a-form>
                         </a-tab-pane>
                         <a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
-                            <a-list item-layout="horizontal" style="background: white">
+                            <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
                                 <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
                             </a-list>
                         </a-tab-pane>
                         <a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'>
-                            <a-list item-layout="horizontal" style="background: white">
+                            <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
                                 <setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}'  v-model="allSetting.tgBotEnable"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.telegramToken"}}' desc='{{ i18n "pages.setting.telegramTokenDesc"}}'  v-model="allSetting.tgBotToken"></setting-list-item>
                                 <setting-list-item type="number" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}'  v-model.number="allSetting.tgBotChatId"></setting-list-item>
@@ -106,7 +106,7 @@
                             </a-list>
                         </a-tab-pane>
                         <a-tab-pane key="5" tab='{{ i18n "pages.setting.otherSetting"}}'>
-                            <a-list item-layout="horizontal" style="background: white">
+                            <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.timeZonee"}}' desc='{{ i18n "pages.setting.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
                             </a-list>
                         </a-tab-pane>

+ 10 - 7
web/service/inbound.go

@@ -275,15 +275,18 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
 
 	for _, traffic := range traffics {
 		inbound := &model.Inbound{}
-
-		err := txInbound.Where("settings like ?", "%"+traffic.Email+"%").First(inbound).Error
-		traffic.InboundId = inbound.Id
+		client := &xray.ClientTraffic{}
+		err := tx.Where("email = ?", traffic.Email).First(client).Error
 		if err != nil {
 			if err == gorm.ErrRecordNotFound {
-				// delete removed client record
-				clientErr := s.DelClientStat(tx, traffic.Email)
-				logger.Warning(err, traffic.Email, clientErr)
-
+				logger.Warning(err, traffic.Email)
+			}
+			continue
+		}
+		err = txInbound.Where("id=?", client.InboundId).First(inbound).Error
+		if err != nil {
+			if err == gorm.ErrRecordNotFound {
+				logger.Warning(err, traffic.Email)
 			}
 			continue
 		}

+ 24 - 0
web/service/server.go

@@ -198,6 +198,30 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
 	return versions, nil
 }
 
+func (s *ServerService) StopXrayService() (string error) {
+
+        err := s.xrayService.StopXray()
+                if err != nil {
+                        logger.Error("stop xray failed:", err)
+                        return err
+                }
+
+	return nil
+}
+
+func (s *ServerService) RestartXrayService() (string error) {
+
+        s.xrayService.StopXray()
+        defer func() {
+                err := s.xrayService.RestartXray(true)
+                if err != nil {
+                        logger.Error("start xray failed:", err)
+		}
+        }()
+
+	return nil
+}
+
 func (s *ServerService) downloadXRay(version string) (string, error) {
 	osName := runtime.GOOS
 	arch := runtime.GOARCH

+ 6 - 3
web/translation/translate.en_US.toml

@@ -71,6 +71,8 @@
 "hard" = "Hard Disk"
 "xrayStatus" = "Xray Status"
 "xraySwitch" = "Switch Version"
+"restartXray" = "Restart"
+"stopXray" = "Stop"
 "xraySwitchClick" = "Click on the version you want to switch"
 "xraySwitchClickDesk" = "Please choose carefully, older versions may have incompatible configurations"
 "operationHours" = "Operation Hours"
@@ -87,6 +89,7 @@
 "dontRefreshh" = "Installation is in progress, please do not refresh this page"
 
 [pages.inbounds]
+"export" = "Export"
 "title" = "Inbounds"
 "totalDownUp" = "Total Uploads/Downloads"
 "totalUsage" = "Total Usage"
@@ -121,10 +124,10 @@
 "noRecommendKeepDefault" = "There are no special requirements to keep the default"
 "certificatePath" = "Certificate File Path"
 "certificateContent" = "Certificate File Content"
-"publicKeyPath" = "Public Key File Path"
+"publicKeyPath" = "Public Key Path"
 "publicKeyContent" = "public Key Content"
-"keyPath" = "Key File Path"
-"keyContent" = "Key Content"
+"keyPath" = "Private key Path"
+"keyContent" = "Private Key Content"
 "client" = "Client"
 "uid" = "UID"
 

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

@@ -71,6 +71,8 @@
 "hard" = "حافظه دیسک"
 "xrayStatus" = "وضعیت Xray"
 "xraySwitch" = "تغییر ورژن"
+"restartXray" = "راه اندازی مجدد"
+"stopXray" = "توقف"
 "xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
 "xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
 "operationHours" = "ساعت فعال"
@@ -88,6 +90,7 @@
 
 
 [pages.inbounds]
+"export" = "استخراج لینکها"
 "title" = "کاربران"
 "totalDownUp" = "جمع آپلود/دانلود"
 "totalUsage" = "جمع کل"

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

@@ -70,6 +70,8 @@
 "hard" = "硬盘"
 "xrayStatus" = "xray 状态"
 "xraySwitch" = "切换版本"
+"restartXray" = "重新开始"
+"stopXray" = "停止"
 "xraySwitchClick" = "点击你想切换的版本"
 "xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
 "operationHours" = "运行时间"
@@ -87,6 +89,7 @@
 
 
 [pages.inbounds]
+"export" = "导出链接"
 "title" = "入站列表"
 "totalDownUp" = "总上传 / 下载"
 "totalUsage" = "总用量"

Some files were not shown because too many files changed in this diff