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:
authorHo3ein <ho3ein.sanaei@gmail.com>2023-05-31 09:17:02 +0300
committerGitHub <noreply@github.com>2023-05-31 09:17:02 +0300
commit94fad02737d82817ca69f1f05872b49e769a0cb4 (patch)
tree55e7ead217c5a3e82791c0edae6ad44de1ba524f /web
parent8442836512d82b705e404bc1749e3000115ba550 (diff)
parentd694e6eafccad246c63264714897316f671d6428 (diff)
Merge pull request #545 from hamid-gh98/main
🔀 New Feature + Fix URLs + Some Improvements 🛠️🌐
Diffstat (limited to 'web')
-rw-r--r--web/assets/js/model/models.js3
-rw-r--r--web/assets/js/util/common.js18
-rw-r--r--web/controller/inbound.go2
-rw-r--r--web/controller/setting.go97
-rw-r--r--web/entity/entity.go1
-rw-r--r--web/global/hashStorage.go2
-rw-r--r--web/html/common/qrcode_modal.html30
-rw-r--r--web/html/xui/inbound_info_modal.html8
-rw-r--r--web/html/xui/inbound_modal.html2
-rw-r--r--web/html/xui/inbounds.html2
-rw-r--r--web/html/xui/settings.html59
-rw-r--r--web/middleware/domainValidator.go21
-rw-r--r--web/middleware/redirect.go34
-rw-r--r--web/service/inbound.go1
-rw-r--r--web/service/setting.go7
-rw-r--r--web/service/tgbot.go47
-rw-r--r--web/translation/translate.en_US.toml7
-rw-r--r--web/translation/translate.fa_IR.toml7
-rw-r--r--web/translation/translate.ru_RU.toml7
-rw-r--r--web/translation/translate.zh_Hans.toml7
-rw-r--r--web/web.go34
21 files changed, 231 insertions, 165 deletions
diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js
index 9a5dcc85..e1a766dc 100644
--- a/web/assets/js/model/models.js
+++ b/web/assets/js/model/models.js
@@ -168,6 +168,7 @@ class AllSetting {
constructor(data) {
this.webListen = "";
+ this.webDomain = "";
this.webPort = 2053;
this.webCertFile = "";
this.webKeyFile = "";
@@ -187,7 +188,7 @@ class AllSetting {
this.subEnable = false;
this.subListen = "";
this.subPort = "2096";
- this.subPath = "sub/";
+ this.subPath = "/sub/";
this.subDomain = "";
this.subCertFile = "";
this.subKeyFile = "";
diff --git a/web/assets/js/util/common.js b/web/assets/js/util/common.js
index 563bfd45..8e30bce7 100644
--- a/web/assets/js/util/common.js
+++ b/web/assets/js/util/common.js
@@ -135,3 +135,21 @@ function doAllItemsExist(array1, array2) {
}
return true;
}
+
+function buildURL({ host, port, isTLS, base, path }) {
+ if (!host || host.length === 0) host = window.location.hostname;
+ if (!port || port.length === 0) port = window.location.port;
+
+ if (isTLS === undefined) isTLS = window.location.protocol === "https:";
+
+ const protocol = isTLS ? "https:" : "http:";
+
+ port = String(port);
+ if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
+ port = "";
+ } else {
+ port = `:${port}`;
+ }
+
+ return `${protocol}//${host}${port}${base}${path}`;
+}
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index 5ce58d53..815f1788 100644
--- a/web/controller/inbound.go
+++ b/web/controller/inbound.go
@@ -65,6 +65,7 @@ func (a *InboundController) getInbounds(c *gin.Context) {
}
jsonObj(c, inbounds, nil)
}
+
func (a *InboundController) getInbound(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
@@ -168,6 +169,7 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
}
jsonMsg(c, "Log Cleared", nil)
}
+
func (a *InboundController) addInboundClient(c *gin.Context) {
data := &model.Inbound{}
err := c.ShouldBind(data)
diff --git a/web/controller/setting.go b/web/controller/setting.go
index 0292c46a..cd509293 100644
--- a/web/controller/setting.go
+++ b/web/controller/setting.go
@@ -65,77 +65,42 @@ func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
}
func (a *SettingController) getDefaultSettings(c *gin.Context) {
- expireDiff, err := a.settingService.GetExpireDiff()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- trafficDiff, err := a.settingService.GetTrafficDiff()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- defaultCert, err := a.settingService.GetCertFile()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- defaultKey, err := a.settingService.GetKeyFile()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- tgBotEnable, err := a.settingService.GetTgbotenabled()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- subEnable, err := a.settingService.GetSubEnable()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- subPort, err := a.settingService.GetSubPort()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- subPath, err := a.settingService.GetSubPath()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- subDomain, err := a.settingService.GetSubDomain()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
- }
- subKeyFile, err := a.settingService.GetSubKeyFile()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
+ type settingFunc func() (interface{}, error)
+
+ settings := map[string]settingFunc{
+ "expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() },
+ "trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() },
+ "defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() },
+ "defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() },
+ "tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() },
+ "subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() },
+ "subPort": func() (interface{}, error) { return a.settingService.GetSubPort() },
+ "subPath": func() (interface{}, error) { return a.settingService.GetSubPath() },
+ "subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
+ "subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
+ "subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
}
- subCertFile, err := a.settingService.GetSubCertFile()
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
- return
+
+ result := make(map[string]interface{})
+
+ for key, fn := range settings {
+ value, err := fn()
+ if err != nil {
+ jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
+ return
+ }
+ result[key] = value
}
+
subTLS := false
- if subKeyFile != "" || subCertFile != "" {
+ if result["subKeyFile"] != "" || result["subCertFile"] != "" {
subTLS = true
}
- result := map[string]interface{}{
- "expireDiff": expireDiff,
- "trafficDiff": trafficDiff,
- "defaultCert": defaultCert,
- "defaultKey": defaultKey,
- "tgBotEnable": tgBotEnable,
- "subEnable": subEnable,
- "subPort": subPort,
- "subPath": subPath,
- "subDomain": subDomain,
- "subTLS": subTLS,
- }
+ result["subTLS"] = subTLS
+
+ delete(result, "subKeyFile")
+ delete(result, "subCertFile")
+
jsonObj(c, result, nil)
}
diff --git a/web/entity/entity.go b/web/entity/entity.go
index 0bfbfd2a..d5e90108 100644
--- a/web/entity/entity.go
+++ b/web/entity/entity.go
@@ -28,6 +28,7 @@ type Pager struct {
type AllSetting struct {
WebListen string `json:"webListen" form:"webListen"`
+ WebDomain string `json:"webDomain" form:"webDomain"`
WebPort int `json:"webPort" form:"webPort"`
WebCertFile string `json:"webCertFile" form:"webCertFile"`
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
diff --git a/web/global/hashStorage.go b/web/global/hashStorage.go
index 9dfea169..5d8135ee 100644
--- a/web/global/hashStorage.go
+++ b/web/global/hashStorage.go
@@ -18,7 +18,6 @@ type HashStorage struct {
sync.RWMutex
Data map[string]HashEntry
Expiration time.Duration
-
}
func NewHashStorage(expiration time.Duration) *HashStorage {
@@ -46,7 +45,6 @@ func (h *HashStorage) SaveHash(query string) string {
return md5HashString
}
-
func (h *HashStorage) GetValue(hash string) (string, bool) {
h.RLock()
defer h.RUnlock()
diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html
index e6b7b998..8edfa2de 100644
--- a/web/html/common/qrcode_modal.html
+++ b/web/html/common/qrcode_modal.html
@@ -68,8 +68,8 @@
qrModal: qrModal,
},
methods: {
- copyToClipboard(elmentId,content) {
- this.qrModal.clipboard = new ClipboardJS('#'+elmentId, {
+ copyToClipboard(elmentId, content) {
+ this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
text: () => content,
});
this.qrModal.clipboard.on('success', () => {
@@ -77,29 +77,25 @@
this.qrModal.clipboard.destroy();
});
},
- setQrCode(elmentId,content) {
+ setQrCode(elmentId, content) {
new QRious({
- element: document.querySelector('#'+elmentId),
- size: 260,
- value: content,
- });
+ 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;
+ const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
+ return buildURL({ host, port, isTLS, base, path: subID });
}
},
updated() {
- if (qrModal.client.subId){
+ if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId;
- this.setQrCode("qrCode-sub",this.genSubLink(qrModal.subId));
+ this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
}
- qrModal.qrcodes.forEach((element,index) => {
- this.setQrCode("qrCode-"+index, element.link);
+ qrModal.qrcodes.forEach((element, index) => {
+ this.setQrCode("qrCode-" + index, element.link);
});
}
});
diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html
index b7b3436b..23bd5af1 100644
--- a/web/html/xui/inbound_info_modal.html
+++ b/web/html/xui/inbound_info_modal.html
@@ -253,12 +253,8 @@
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 { domain: host, port, tls: isTLS, path: base } = app.subSettings;
+ return buildURL({ host, port, isTLS, base, path: subID });
}
};
diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html
index 25e19473..11e6020c 100644
--- a/web/html/xui/inbound_modal.html
+++ b/web/html/xui/inbound_modal.html
@@ -96,7 +96,7 @@
set multiDomain(value) {
if (value) {
inModal.inbound.stream.tls.server = "";
- inModal.inbound.stream.tls.settings.domains = [{remark: "", domain: window.location.host.split(":")[0]}];
+ inModal.inbound.stream.tls.settings.domains = [{ remark: "", domain: window.location.hostname }];
} else {
inModal.inbound.stream.tls.server = "";
inModal.inbound.stream.tls.settings.domains = [];
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index 7b9ba207..329f0f46 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -311,7 +311,7 @@
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
- { title: 'UID', width: 120, dataIndex: "id" },
+ { title: 'UUID', width: 120, dataIndex: "id" },
];
const innerTrojanColumns = [
diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html
index d78533a1..745959a2 100644
--- a/web/html/xui/settings.html
+++ b/web/html/xui/settings.html
@@ -91,6 +91,7 @@
</a-row>
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
+ <setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
@@ -306,23 +307,37 @@
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
</a-collapse-panel>
- <a-collapse-panel header='{{ i18n "pages.settings.templates.manualLists"}}'>
- <a-row :xs="24" :sm="24" :lg="12">
- <h2 class="collapse-title">
- <a-icon type="warning"></a-icon>
- {{ i18n "pages.settings.templates.manualListsDesc" }}
- </h2>
- </a-row>
- <setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualBlockedIPs"}}' v-model="manualBlockedIPs"></setting-list-item>
- <setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualBlockedDomains"}}' v-model="manualBlockedDomains"></setting-list-item>
- <setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectIPs"}}' v-model="manualDirectIPs"></setting-list-item>
- <setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectDomains"}}' v-model="manualDirectDomains"></setting-list-item>
- <setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualIPv4Domains"}}' v-model="manualIPv4Domains"></setting-list-item>
- <setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualWARPDomains"}}' v-model="manualWARPDomains"></setting-list-item>
+ </a-collapse>
+ </a-tab-pane>
+ <a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.manualLists"}}' style="padding-top: 20px;">
+ <a-row :xs="24" :sm="24" :lg="12">
+ <h2 class="collapse-title">
+ <a-icon type="warning"></a-icon>
+ {{ i18n "pages.settings.templates.manualListsDesc" }}
+ </h2>
+ </a-row>
+ <a-collapse>
+ <a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedIPs"}}'>
+ <setting-list-item type="textarea" v-model="manualBlockedIPs"></setting-list-item>
+ </a-collapse-panel>
+ <a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedDomains"}}'>
+ <setting-list-item type="textarea" v-model="manualBlockedDomains"></setting-list-item>
+ </a-collapse-panel>
+ <a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectIPs"}}'>
+ <setting-list-item type="textarea" v-model="manualDirectIPs"></setting-list-item>
+ </a-collapse-panel>
+ <a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectDomains"}}'>
+ <setting-list-item type="textarea" v-model="manualDirectDomains"></setting-list-item>
+ </a-collapse-panel>
+ <a-collapse-panel header='{{ i18n "pages.settings.templates.manualIPv4Domains"}}'>
+ <setting-list-item type="textarea" v-model="manualIPv4Domains"></setting-list-item>
+ </a-collapse-panel>
+ <a-collapse-panel header='{{ i18n "pages.settings.templates.manualWARPDomains"}}'>
+ <setting-list-item type="textarea" v-model="manualWARPDomains"></setting-list-item>
</a-collapse-panel>
</a-collapse>
</a-tab-pane>
- <a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
+ <a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
<a-collapse>
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}'>
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
@@ -335,7 +350,7 @@
</a-collapse-panel>
</a-collapse>
</a-tab-pane>
- <a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
+ <a-tab-pane key="tpl-4" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
</a-tab-pane>
</a-tabs>
@@ -391,9 +406,9 @@
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
+ <setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
- <setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
@@ -522,7 +537,7 @@
this.loading(false);
if (msg.success) {
this.user = {};
- window.location.replace(basePath + "logout")
+ window.location.replace(basePath + "logout");
}
},
async restartPanel() {
@@ -541,12 +556,10 @@
if (msg.success) {
this.loading(true);
await PromiseUtil.sleep(5000);
- let protocol = "http://";
- if (this.allSetting.webCertFile !== "") {
- protocol = "https://";
- }
- const { host } = window.location;
- window.location.replace(protocol + host + this.allSetting.webBasePath + "panel/settings");
+ const { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
+ const isTLS = webCertFile !== "" || webKeyFile !== "";
+ const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
+ window.location.replace(url);
}
},
async fetchUserSecret() {
diff --git a/web/middleware/domainValidator.go b/web/middleware/domainValidator.go
new file mode 100644
index 00000000..3adb0f0f
--- /dev/null
+++ b/web/middleware/domainValidator.go
@@ -0,0 +1,21 @@
+package middleware
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ host := strings.Split(c.Request.Host, ":")[0]
+
+ if host != domain {
+ c.AbortWithStatus(http.StatusForbidden)
+ return
+ }
+
+ c.Next()
+ }
+}
diff --git a/web/middleware/redirect.go b/web/middleware/redirect.go
new file mode 100644
index 00000000..e3dc8ada
--- /dev/null
+++ b/web/middleware/redirect.go
@@ -0,0 +1,34 @@
+package middleware
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+func RedirectMiddleware(basePath string) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // Redirect from old '/xui' path to '/panel'
+ redirects := map[string]string{
+ "panel/API": "panel/api",
+ "xui/API": "panel/api",
+ "xui": "panel",
+ }
+
+ path := c.Request.URL.Path
+ for from, to := range redirects {
+ from, to = basePath+from, basePath+to
+
+ if strings.HasPrefix(path, from) {
+ newPath := to + path[len(from):]
+
+ c.Redirect(http.StatusMovedPermanently, newPath)
+ c.Abort()
+ return
+ }
+ }
+
+ c.Next()
+ }
+}
diff --git a/web/service/inbound.go b/web/service/inbound.go
index 6a182fcf..11522ad2 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -1185,6 +1185,7 @@ func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error)
}
return InboundClientIps.Ips, nil
}
+
func (s *InboundService) ClearClientIps(clientEmail string) error {
db := database.GetDB()
diff --git a/web/service/setting.go b/web/service/setting.go
index 593b23be..677ccbb2 100644
--- a/web/service/setting.go
+++ b/web/service/setting.go
@@ -24,6 +24,7 @@ var xrayTemplateConfig string
var defaultValueMap = map[string]string{
"xrayTemplateConfig": xrayTemplateConfig,
"webListen": "",
+ "webDomain": "",
"webPort": "2053",
"webCertFile": "",
"webKeyFile": "",
@@ -44,7 +45,7 @@ var defaultValueMap = map[string]string{
"subEnable": "false",
"subListen": "",
"subPort": "2096",
- "subPath": "sub/",
+ "subPath": "/sub/",
"subDomain": "",
"subCertFile": "",
"subKeyFile": "",
@@ -225,6 +226,10 @@ func (s *SettingService) GetListen() (string, error) {
return s.getString("webListen")
}
+func (s *SettingService) GetWebDomain() (string, error) {
+ return s.getString("webDomain")
+}
+
func (s *SettingService) GetTgBotToken() (string, error) {
return s.getString("tgBotToken")
}
diff --git a/web/service/tgbot.go b/web/service/tgbot.go
index e0261775..d1a1e3fe 100644
--- a/web/service/tgbot.go
+++ b/web/service/tgbot.go
@@ -77,13 +77,15 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
return err
}
- for _, adminId := range strings.Split(tgBotid, ",") {
- id, err := strconv.Atoi(adminId)
- if err != nil {
- logger.Warning("Failed to get IDs from GetTgBotChatId:", err)
- return err
+ if tgBotid != "" {
+ for _, adminId := range strings.Split(tgBotid, ",") {
+ id, err := strconv.Atoi(adminId)
+ if err != nil {
+ logger.Warning("Failed to get IDs from GetTgBotChatId:", err)
+ return err
+ }
+ adminIds = append(adminIds, int64(id))
}
- adminIds = append(adminIds, int64(id))
}
bot, err = telego.NewBot(tgBottoken)
@@ -188,7 +190,7 @@ func (t *Tgbot) OnReceive() {
}
func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) {
- msg := ""
+ msg, onlyMessage := "", false
command, commandArgs := tu.ParseCommand(message.Text)
@@ -204,8 +206,13 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
}
msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose")
case "status":
+ onlyMessage = true
msg += t.I18nBot("tgbot.commands.status")
+ case "id":
+ onlyMessage = true
+ msg += t.I18nBot("tgbot.commands.getID", "ID=="+strconv.FormatInt(message.From.ID, 10))
case "usage":
+ onlyMessage = true
if len(commandArgs) > 0 {
if isAdmin {
t.searchClient(chatId, commandArgs[0])
@@ -216,6 +223,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
msg += t.I18nBot("tgbot.commands.usage")
}
case "inbound":
+ onlyMessage = true
if isAdmin && len(commandArgs) > 0 {
t.searchInbound(chatId, commandArgs[0])
} else {
@@ -224,6 +232,11 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
default:
msg += t.I18nBot("tgbot.commands.unknown")
}
+
+ if onlyMessage {
+ t.SendMsgToTgbot(chatId, msg)
+ return
+ }
t.SendAnswer(chatId, msg, isAdmin)
}
@@ -498,6 +511,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
if !isRunning {
return
}
+
if msg == "" {
logger.Info("[tgbot] message is empty!")
return
@@ -723,7 +737,7 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string, tgUserID string)
output := ""
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
- if (traffic.Enable) {
+ if traffic.Enable {
output += t.I18nBot("tgbot.messages.active")
if flag {
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
@@ -791,6 +805,7 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
output := ""
output += t.I18nBot("tgbot.messages.email", "Email=="+email)
output += t.I18nBot("tgbot.messages.TGUser", "TelegramID=="+tgId)
+ output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@@ -840,7 +855,7 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
flag := false
diff := traffic.ExpiryTime/1000 - now
if traffic.ExpiryTime == 0 {
- expiryTime = t.I18nBot("tgbot.unlimited")
+ expiryTime = t.I18nBot("tgbot.unlimited")
} else if diff > 172800 || !traffic.Enable {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
} else if traffic.ExpiryTime < 0 {
@@ -860,7 +875,7 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
output := ""
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
- if (traffic.Enable) {
+ if traffic.Enable {
output += t.I18nBot("tgbot.messages.active")
if flag {
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
@@ -918,7 +933,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
t.SendMsgToTgbot(chatId, msg)
return
}
-
+
now := time.Now().Unix()
for _, inbound := range inbouds {
info := ""
@@ -958,7 +973,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
output := ""
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
- if (traffic.Enable) {
+ if traffic.Enable {
output += t.I18nBot("tgbot.messages.active")
if flag {
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
@@ -998,7 +1013,7 @@ func (t *Tgbot) searchForClient(chatId int64, query string) {
flag := false
diff := traffic.ExpiryTime/1000 - now
if traffic.ExpiryTime == 0 {
- expiryTime = t.I18nBot("tgbot.unlimited")
+ expiryTime = t.I18nBot("tgbot.unlimited")
} else if diff > 172800 || !traffic.Enable {
expiryTime = time.Un