瀏覽代碼

[feature] add multi domain tls (CDN ready)

Co-Authored-By: Alireza Ahmadi <[email protected]>
MHSanaei 1 年之前
父節點
當前提交
1fa9101b40

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

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

+ 16 - 21
web/assets/js/model/xray.js

@@ -498,8 +498,7 @@ class TlsStreamSettings extends XrayCommonClass {
         }
 
 		if (!ObjectUtil.isEmpty(json.settings)) {
-            settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
-        }
+            settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName, json.settings.domains);        }
         return new TlsStreamSettings(
             json.serverName,
             json.minVersion,
@@ -566,17 +565,19 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
 };
 
 TlsStreamSettings.Settings = class extends XrayCommonClass {
-    constructor(allowInsecure = false, fingerprint = '', serverName = '') {
+    constructor(allowInsecure = false, fingerprint = '', serverName = '', domains = []) {
         super();
         this.allowInsecure = allowInsecure;
         this.fingerprint = fingerprint;
         this.serverName = serverName;
+        this.domains = domains;
     }
     static fromJson(json = {}) {
         return new TlsStreamSettings.Settings(
             json.allowInsecure,
             json.fingerprint,
-            json.servername,
+            json.serverName,
+            json.domains,
         );
     }
     toJson() {
@@ -584,6 +585,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
             allowInsecure: this.allowInsecure,
             fingerprint: this.fingerprint,
             serverName: this.serverName,
+            domains: this.domains,
         };
     }
 };
@@ -1507,25 +1509,13 @@ class Inbound extends XrayCommonClass {
 
     genLink(address='', remark='', clientIndex=0) {
         switch (this.protocol) {
-            case Protocols.VMESS:
-                if (this.settings.vmesses[clientIndex].email != ""){
-                    remark += '-' + this.settings.vmesses[clientIndex].email
-                }
+            case Protocols.VMESS:  
                 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: 
-                if (this.settings.shadowsockses[clientIndex].email != ""){
-                    remark = this.settings.shadowsockses[clientIndex].email
-                }
                 return this.genSSLink(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 '';
         }
@@ -1537,12 +1527,17 @@ class Inbound extends XrayCommonClass {
             case Protocols.VMESS:
             case Protocols.VLESS:
             case Protocols.TROJAN:
-                JSON.parse(this.settings).clients.forEach((_,index) => {
-                    link += this.genLink(address, remark, index) + '\r\n';
+            case Protocols.SHADOWSOCKS:
+                JSON.parse(this.settings).clients.forEach((client,index) => {
+                    if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
+                        this.stream.tls.settings.domains.forEach((domain) => {
+                            link += this.genLink(domain.domain, remark + '-' + client.email + '-' + domain.remark, index) + '\r\n';
+                        });
+                    } else {
+                        link += this.genLink(address, remark + '-' + client.email, index) + '\r\n';
+                    }
                 });
                 return link;
-            case Protocols.SHADOWSOCKS:
-                return (this.genSSLink(address, remark) + '\r\n');
             default: return '';
         }
     }

+ 7 - 0
web/controller/inbound.go

@@ -146,11 +146,18 @@ func (a *InboundController) getClientIps(c *gin.Context) {
 
 	ips, err := a.inboundService.GetInboundClientIps(email)
 	if err != nil {
+		jsonObj(c, "Failed to get client IPs", nil)
+		return
+	}
+
+	if ips == "" {
 		jsonObj(c, "No IP Record", nil)
 		return
 	}
+
 	jsonObj(c, ips, nil)
 }
+
 func (a *InboundController) clearClientIps(c *gin.Context) {
 	email := c.Param("email")
 

+ 58 - 28
web/html/common/qrcode_modal.html

@@ -7,47 +7,53 @@
     <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
         {{ i18n "pages.inbounds.clickOnQRcode" }}
     </a-tag>
-    <a-tag v-if="qrModal.clientName" color="orange" style="margin-bottom: 10px;display: block;text-align: center;">
-        {{ i18n "pages.inbounds.email" }}: "[[ qrModal.clientName ]]"
-    </a-tag>
-    <canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%; margin-top: 10px;"></canvas>
+    <template v-if="app.subSettings.enable && qrModal.subId">
+        <a-divider>Subscription</a-divider>
+        <canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas>
+    </template>
+    <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
+    <template v-for="(row, index) in qrModal.qrcodes">
+        <a-tag color="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
+        <canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
+    </template>
 </a-modal>
 
 <script>
 
     const qrModal = {
         title: '',
-        content: '',
+        clientIndex: 0,
         inbound: new Inbound(),
         dbInbound: new DBInbound(),
-        copyText: '',
-        clientName: null,
-        qrcode: null,
+        client: null,
+        qrcodes: [],
         clipboard: null,
         visible: false,
-        show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '', clientName = null) {
+        subId: '',
+        show: function (title = '', dbInbound = new DBInbound(), clientIndex = 0) {
             this.title = title;
-            this.content = content;
+            this.clientIndex = clientIndex;
             this.dbInbound = dbInbound;
             this.inbound = dbInbound.toInbound();
-            this.clientName = clientName;
-            if (ObjectUtil.isEmpty(copyText)) {
-                this.copyText = content;
+            settings = JSON.parse(this.inbound.settings);
+            this.client = settings.clients[clientIndex];
+            remark = this.dbInbound.remark + "-" + this.client.email;
+            address = this.dbInbound.address;
+            this.qrcodes = [];
+            if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
+                this.inbound.stream.tls.settings.domains.forEach((domain) => {
+                    this.qrcodes.push({
+                        remark: remark + "-" + domain.remark,
+                        link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, clientIndex)
+                    });
+                });
             } else {
-                this.copyText = copyText;
+                this.qrcodes.push({
+                    remark: remark,
+                    link: this.inbound.genLink(address, remark, clientIndex)
+                });
             }
             this.visible = true;
-            qrModalApp.$nextTick(() => {
-                if (this.qrcode === null) {
-                    this.qrcode = new QRious({
-                        element: document.querySelector('#qrCode'),
-                        size: 260,
-                        value: content,
-                    });
-                } else {
-                    this.qrcode.value = content;
-                }
-            });
         },
         close: function () {
             this.visible = false;
@@ -61,16 +67,40 @@
             qrModal: qrModal,
         },
         methods: {
-            copyToClipboard() {
-                this.qrModal.clipboard = new ClipboardJS('#qrCode', {
-                    text: () => this.qrModal.copyText,
+            copyToClipboard(elmentId,content) {
+                this.qrModal.clipboard = new ClipboardJS('#'+elmentId, {
+                    text: () => content,
                 });
                 this.qrModal.clipboard.on('success', () => {
                     app.$message.success('{{ i18n "copied" }}')
                     this.qrModal.clipboard.destroy();
                 });
+            },
+            setQrCode(elmentId,content) {
+                new QRious({
+                        element: document.querySelector('#'+elmentId),
+                        size: 260,
+                        value: content,
+                    });
+            },
+            genSubLink(subID) {
+                protocol = app.subSettings.tls ? "https://" : "http://";
+                hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain;
+                subPort = app.subSettings.port;
+                port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort);
+                subPath = app.subSettings.path;
+                return protocol + hostName + port + subPath + subID;
             }
         },
+        updated() {
+            if (qrModal.client.subId){
+                qrModal.subId = qrModal.client.subId;
+                this.setQrCode("qrCode-sub",this.genSubLink(this.subId));
+            }
+            qrModal.qrcodes.forEach((element,index) => {
+                this.setQrCode("qrCode-"+index, element.link);     
+            });
+        }
     });
 
 </script>

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

@@ -33,7 +33,21 @@
 
 <!-- tls settings -->
 <a-form v-if="inbound.tls" layout="inline">
-    <a-form-item label='{{ i18n "domainName" }}'>
+    <a-form-item label='Multi Domain'>
+        <a-switch v-model="multiDomain"></a-switch>
+        <a-button v-if="multiDomain" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})">+</a-button>
+    </a-form-item>
+    <a-form-item v-if="multiDomain" label='Domains'>
+        <a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
+            <a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
+            <a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
+                <template slot="addonAfter">
+                    <a-button size="small" style="margin: 0px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
+                </template>
+            </a-input>
+        </a-input-group>
+    </a-form-item>
+    <a-form-item v-else label='{{ i18n "domainName" }}'>
         <a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
     </a-form-item>
     <a-form-item label="CipherSuites">

+ 1 - 1
web/html/xui/inbound_client_table.html

@@ -29,7 +29,7 @@
     <a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
 </template>                                    
 <template slot="traffic" slot-scope="text, client">
-    <a-tag :color="statsColor(record, client.email)" @click="alert(usageColor(0,1024,512))">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
+    <a-tag :color="statsColor(record, client.email)">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
     <template v-if="client._totalGB > 0">
         <a-tag :color="statsColor(record, client.email)">[[client._totalGB]]GB</a-tag>
     </template>

+ 55 - 24
web/html/xui/inbound_info_modal.html

@@ -59,10 +59,9 @@
             </td>
             <td v-else-if="inbound.reality">
                 reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
-                reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
+                reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
             </td>
-            <td v-else>
-                tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
+            <td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
             </td>
         </tr>
     </table>
@@ -110,17 +109,16 @@
             </td>
         </tr>
     </table>
-    <table v-if="infoModal.clientSettings.subId + infoModal.clientSettings.tgId" style="margin-bottom: 10px;">
-        <tr v-if="infoModal.clientSettings.subId">
-            <td>Subscription link</td>
-            <td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
-            <td><a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', subBase + infoModal.clientSettings.subId)"></a-icon></td>
-        </tr>
-        <tr v-if="infoModal.clientSettings.tgId">
-            <td>Telegram ID</td>
-            <td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
-        </tr>
-    </table>
+    <template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
+        <a-divider>Subscription link</a-divider>
+        <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
+        <a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"></a-icon>
+    </template>
+    <template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
+        <a-divider>Telegram Username</a-divider>
+        <a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a>
+        <a-icon id="copy-tg-link" type="snippets" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)"></a-icon>
+    </template>
     </template>
     <template v-else>
         <a-divider></a-divider>
@@ -190,8 +188,14 @@
     </template>
     <div v-if="dbInbound.hasLink()">
         <a-divider>URL</a-divider>
-        <p>[[ infoModal.link ]]</p>
-        <button class="ant-btn ant-btn-primary" id="copy-url-link" @click="copyToClipboard('copy-url-link', infoModal.link)"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
+        <a-row v-for="(link,index) in infoModal.links">
+            <a-col :span="21"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
+            <a-col :span="3" style="text-align: right;">
+                <button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
+                    <a-icon type="snippets"></a-icon>{{ i18n "copy" }}
+                </button>
+            </a-col>
+        </a-row>
     </div>
 </a-modal>
 <script>
@@ -206,23 +210,56 @@
         upStats: 0,
         downStats: 0,
         clipboard: null,
-        link: null,
+        links: [],
         index: null,
         isExpired: false,
+        subLink: '',
+        tgLink: '',
         show(dbInbound, index) {
             this.index = index;
             this.inbound = dbInbound.toInbound();
             this.dbInbound = new DBInbound(dbInbound);
-            this.link = dbInbound.genLink(index);
             this.settings = JSON.parse(this.inbound.settings);
             this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
             this.isExpired = this.inbound.isExpiry(index);
             this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
+            remark = this.dbInbound.remark + "-" + this.clientSettings.email;
+            address = this.dbInbound.address;
+            this.links = [];
+            if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
+                this.inbound.stream.tls.settings.domains.forEach((domain) => {
+                    this.links.push({
+                        remark: remark + "-" + domain.remark,
+                        link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, index)
+                    });
+                });
+            } else {
+                this.links.push({
+                    remark: remark,
+                    link: this.inbound.genLink(address, remark, index)
+                });
+            }
+            if (this.clientSettings) {
+                if (this.clientSettings.subId) {
+                    this.subLink = this.genSubLink(this.clientSettings.subId);
+                }
+                if (this.clientSettings.tgId) {
+                    this.tgLink = "https://t.me/" + this.clientSettings.tgId;
+                }
+            }
             this.visible = true;
         },
         close() {
             infoModal.visible = false;
         },
+        genSubLink(subID) {
+            protocol = app.subSettings.tls ? "https://" : "http://";
+            hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain;
+            subPort = app.subSettings.port;
+            port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort);
+            subPath = app.subSettings.path;
+            return protocol + hostName + port + subPath + subID;
+        }
     };
 
     const infoModalApp = new Vue({
@@ -248,12 +285,6 @@
                 }
                 return infoModal.dbInbound.isEnable;
             },
-            get subBase() {
-                return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "") + basePath + "sub/";
-            },
-            get tgBase() {
-                return "https://t.me/"
-            },
         },
         methods: {
             copyToClipboard(elmentId, content) {

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

@@ -90,6 +90,18 @@
             set delayedExpireDays(days) {
                 this.client.expiryTime = -86400000 * days;
             },
+            get multiDomain() {
+                return this.inbound.stream.tls.settings.domains.length > 0;
+            },
+            set multiDomain(value) {
+                if (value) {
+                    inModal.inbound.stream.tls.server = "";
+                    inModal.inbound.stream.tls.settings.domains = [{remark: "", domain: window.location.host.split(":")[0]}];
+                } else {
+                    inModal.inbound.stream.tls.server = "";
+                    inModal.inbound.stream.tls.settings.domains = [];
+                }
+            }
         },
         methods: {
             streamNetworkChange() {

+ 1 - 3
web/html/xui/inbounds.html

@@ -746,9 +746,7 @@
                 }
             },
             showQrcode(dbInbound, clientIndex) {
-                const clientName = JSON.parse(dbInbound.settings).clients[clientIndex].email;
-                const link = dbInbound.genLink(clientIndex);
-                qrModal.show('{{ i18n "qrCode"}}', link, dbInbound, '', clientName);
+                qrModal.show('{{ i18n "qrCode"}}', dbInbound, clientIndex);
             },
             showInfo(dbInbound, index) {
                 infoModal.show(dbInbound, index);