Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorMHSanaei <ho3ein.sanaei@gmail.com>2023-05-22 17:01:41 +0300
committerMHSanaei <ho3ein.sanaei@gmail.com>2023-05-22 17:01:41 +0300
commit1fa9101b405ad1ba0127317ea4f8a151048b97ee (patch)
treee03a93684953b3473a717a283953464bf368008a /web
parent3f2e1aede90984d3bafab377509f712e5ce51ec0 (diff)
[feature] add multi domain tls (CDN ready)
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
Diffstat (limited to 'web')
-rw-r--r--web/assets/js/model/models.js4
-rw-r--r--web/assets/js/model/xray.js37
-rw-r--r--web/controller/inbound.go7
-rw-r--r--web/html/common/qrcode_modal.html86
-rw-r--r--web/html/xui/form/tls_settings.html16
-rw-r--r--web/html/xui/inbound_client_table.html2
-rw-r--r--web/html/xui/inbound_info_modal.html79
-rw-r--r--web/html/xui/inbound_modal.html12
-rw-r--r--web/html/xui/inbounds.html4
9 files changed, 167 insertions, 80 deletions
diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js
index dc4c85d0..d8395b56 100644
--- a/web/assets/js/model/models.js
+++ b/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() {
diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js
index 9a8fae80..bc8d4d4a 100644
--- a/web/assets/js/model/xray.js
+++ b/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 '';
}
}
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index d13e40bc..5ce58d53 100644
--- a/web/controller/inbound.go
+++ b/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")
diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html
index 855c349a..3271f3ad 100644
--- a/web/html/common/qrcode_modal.html
+++ b/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>
diff --git a/web/html/xui/form/tls_settings.html b/web/html/xui/form/tls_settings.html
index d80e820e..31e559da 100644
--- a/web/html/xui/form/tls_settings.html
+++ b/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">
diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html
index 4b09b43a..bb825437 100644
--- a/web/html/xui/inbound_client_table.html
+++ b/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>
diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html
index be2bcf4a..b7b3436b 100644
--- a/web/html/xui/inbound_info_modal.html
+++ b/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) {
diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html
index 73ed6234..25e19473 100644
--- a/web/html/xui/inbound_modal.html
+++ b/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() {
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index a66e84a9..b15798d4 100644
--- a/web/html/xui/inbounds.html
+++ b/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);