Browse Source

Merge pull request #420 from hamid-gh98/main

[fix] russia domains in settings and More....
Ho3ein 1 year ago
parent
commit
5468069bef

+ 36 - 9
README.md

@@ -1,4 +1,5 @@
-# 3x-ui 
+# 3x-ui
+
 > **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
 
 [![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
@@ -12,7 +13,8 @@
 **If you think this project is helpful to you, you may wish to give a** :star2:
 
 **Buy Me a Coffee :**
- - Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
+
+- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
 
 # Install & Upgrade
 
@@ -47,12 +49,12 @@ or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
 
 Before you set ssl on settings
 
-- http://ip:2053/xui
-- http://domain:2053/xui
+- http://ip:2053/panel
+- http://domain:2053/panel
 
 After you set ssl on settings
 
-- https://yourdomain:2053/xui
+- https://yourdomain:2053/panel
 
 # Environment Variables
 
@@ -69,6 +71,31 @@ Example:
 XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
 ```
 
+# Install with Docker
+
+1. Install Docker:
+   ```sh
+   bash <(curl -sSL https://get.docker.com)
+   ```
+2. Run 3x-ui:
+
+   ```sh
+   docker compose up -d
+   ```
+
+   OR
+
+   ```sh
+   docker run -itd \
+      -e XRAY_VMESS_AEAD_FORCED=false \
+      -v $PWD/db/:/etc/x-ui/ \
+      -v $PWD/cert/:/root/cert/ \
+      --network=host \
+      --restart=unless-stopped \
+      --name 3x-ui \
+      ghcr.io/mhsanaei/3x-ui:latest
+   ```
+
 # Xray Configurations:
 
 **copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
@@ -173,19 +200,19 @@ Reference syntax:
 | `POST` | `"/clientIps/:email"`              | Client Ip address                           |
 | `POST` | `"/clearClientIps/:email"`         | Clear Client Ip address                     |
 | `POST` | `"/addClient"`                     | Add Client to inbound                       |
-| `POST` | `"/:id/delClient/:clientId"`       | Delete Client by clientId*                  |
-| `POST` | `"/updateClient/:clientId"`        | Update Client by clientId*                  |
+| `POST` | `"/:id/delClient/:clientId"`       | Delete Client by clientId\*                 |
+| `POST` | `"/updateClient/:clientId"`        | Update Client by clientId\*                 |
 | `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic                      |
 | `POST` | `"/resetAllTraffics"`              | Reset traffics of all inbounds              |
 | `POST` | `"/resetAllClientTraffics/:id"`    | Reset traffics of all clients in an inbound |
 | `POST` | `"/delDepletedClients/:id"`        | Delete inbound depleted clients (-1: all)   |
 
-*- The field `clientId` should be filled by: 
+\*- The field `clientId` should be filled by:
+
 - `client.id` for VMESS and VLESS
 - `client.password` for TROJAN
 - `client.email` for Shadowsocks
 
-
 - [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
 
 # A Special Thanks To

+ 15 - 0
docker-compose.yml

@@ -0,0 +1,15 @@
+---
+version: "3.9"
+
+services:
+  3x-ui:
+    image: ghcr.io/mhsanaei/3x-ui:latest
+    container_name: 3x-ui
+    volumes:
+      - $PWD/db/:/etc/x-ui/
+      - $PWD/cert/:/root/cert/
+    environment:
+      XRAY_VMESS_AEAD_FORCED: "false"
+    tty: true
+    network_mode: host
+    restart: unless-stopped

+ 1 - 2
web/controller/index.go

@@ -39,7 +39,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
 
 func (a *IndexController) index(c *gin.Context) {
 	if session.IsLogin(c) {
-		c.Redirect(http.StatusTemporaryRedirect, "xui/")
+		c.Redirect(http.StatusTemporaryRedirect, "panel/")
 		return
 	}
 	html(c, "login.html", "pages.login.title", nil)
@@ -101,5 +101,4 @@ func (a *IndexController) getSecretStatus(c *gin.Context) {
 	if err == nil {
 		jsonObj(c, status, nil)
 	}
-
 }

+ 18 - 0
web/controller/setting.go

@@ -3,6 +3,7 @@ package controller
 import (
 	"errors"
 	"time"
+	"x-ui/util/common"
 	"x-ui/web/entity"
 	"x-ui/web/service"
 	"x-ui/web/session"
@@ -44,6 +45,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
 	g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
 	g.POST("/updateUserSecret", a.updateSecret)
 	g.POST("/getUserSecret", a.getUserSecret)
+	g.GET("/searchDatafiles", a.searchDatafiles)
 }
 
 func (a *SettingController) getAllSetting(c *gin.Context) {
@@ -149,6 +151,7 @@ func (a *SettingController) updateSecret(c *gin.Context) {
 	}
 	jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), err)
 }
+
 func (a *SettingController) getUserSecret(c *gin.Context) {
 	loginUser := session.GetLoginUser(c)
 	user := a.userService.GetUserSecret(loginUser.Id)
@@ -156,3 +159,18 @@ func (a *SettingController) getUserSecret(c *gin.Context) {
 		jsonObj(c, user, nil)
 	}
 }
+
+func (a *SettingController) searchDatafiles(c *gin.Context) {
+	searchString := c.Query("query")
+	if searchString == "" {
+		err := common.NewError("data query parameter is empty")
+		jsonMsg(c, "Invalid query:", err)
+		return
+	}
+	found, err := a.settingService.SearchDatafiles(searchString)
+	if err != nil {
+		jsonMsg(c, "Something went wrong!", err)
+		return
+	}
+	jsonObj(c, found, nil)
+}

+ 1 - 1
web/controller/xui.go

@@ -18,7 +18,7 @@ func NewXUIController(g *gin.RouterGroup) *XUIController {
 }
 
 func (a *XUIController) initRouter(g *gin.RouterGroup) {
-	g = g.Group("/xui")
+	g = g.Group("/panel")
 	g.Use(a.checkLogin)
 
 	g.GET("/", a.index)

+ 8 - 2
web/html/common/qrcode_modal.html

@@ -7,7 +7,10 @@
     <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
         {{ i18n "pages.inbounds.clickOnQRcode" }}
     </a-tag>
-    <canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas>
+    <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>
 </a-modal>
 
 <script>
@@ -18,14 +21,16 @@
         inbound: new Inbound(),
         dbInbound: new DBInbound(),
         copyText: '',
+        clientName: null,
         qrcode: null,
         clipboard: null,
         visible: false,
-        show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '') {
+        show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '', clientName = null) {
             this.title = title;
             this.content = content;
             this.dbInbound = dbInbound;
             this.inbound = dbInbound.toInbound();
+            this.clientName = clientName;
             if (ObjectUtil.isEmpty(copyText)) {
                 this.copyText = content;
             } else {
@@ -50,6 +55,7 @@
     };
 
     const qrModalApp = new Vue({
+        delimiters: ['[[', ']]'],
         el: '#qrcode-modal',
         data: {
             qrModal: qrModal,

+ 4 - 4
web/html/login.html

@@ -46,7 +46,7 @@
 
 </style>
 <body>
-<a-layout id="app" v-cloak :class="themeSwitcher.darkClass">
+<a-layout id="app" v-cloak :class="themeSwitcher.darkCardClass">
     <transition name="list" appear>
         <a-layout-content>
             <a-row type="flex" justify="center">
@@ -120,10 +120,10 @@
             secretEnable: false,
             lang: ""
         },
-        created() {
+        async created() {
             this.updateBackground();
             this.lang = getLang();
-            this.secretEnable = this.getSecretStatus();
+            this.secretEnable = await this.getSecretStatus();
         },
         methods: {
             async login() {
@@ -131,7 +131,7 @@
                 const msg = await HttpUtil.post('/login', this.user);
                 this.loading = false;
                 if (msg.success) {
-                    location.href = basePath + 'xui/';
+                    location.href = basePath + 'panel/';
                 }
             },
             async getSecretStatus() {

+ 3 - 3
web/html/xui/client_modal.html

@@ -135,7 +135,7 @@
                 client.email = string;
             },
             async getDBClientIps(email, event) {
-                const msg = await HttpUtil.post('/xui/inbound/clientIps/' + email);
+                const msg = await HttpUtil.post('/panel/inbound/clientIps/' + email);
                 if (!msg.success) {
                     return;
                 }
@@ -149,7 +149,7 @@
                 }
             },
             async clearDBClientIps(email) {
-                const msg = await HttpUtil.post('/xui/inbound/clearClientIps/' + email);
+                const msg = await HttpUtil.post('/panel/inbound/clearClientIps/' + email);
                 if (!msg.success) {
                     return;
                 }
@@ -164,7 +164,7 @@
                     cancelText: '{{ i18n "cancel"}}',
                     onOk: async () => {
                         iconElement.disabled = true;
-                        const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
+                        const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
                         if (msg.success) {
                             this.clientModal.clientStats.up = 0;
                             this.clientModal.clientStats.down = 0;

+ 4 - 4
web/html/xui/common_sider.html

@@ -1,17 +1,17 @@
 {{define "menuItems"}}
-<a-menu-item key="{{ .base_path }}xui/">
+<a-menu-item key="{{ .base_path }}panel/">
     <a-icon type="dashboard"></a-icon>
     <span>{{ i18n "menu.dashboard"}}</span>
 </a-menu-item>
-<a-menu-item key="{{ .base_path }}xui/inbounds">
+<a-menu-item key="{{ .base_path }}panel/inbounds">
     <a-icon type="user"></a-icon>
     <span>{{ i18n "menu.inbounds"}}</span>
 </a-menu-item>
-<a-menu-item key="{{ .base_path }}xui/settings">
+<a-menu-item key="{{ .base_path }}panel/settings">
     <a-icon type="setting"></a-icon>
     <span>{{ i18n "menu.settings"}}</span>
 </a-menu-item>
-<!--<a-menu-item key="{{ .base_path }}xui/clients">-->
+<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
 <!--    <a-icon type="laptop"></a-icon>-->
 <!--    <span>Client</span>-->
 <!--</a-menu-item>-->

+ 2 - 2
web/html/xui/component/themeSwitch.html

@@ -28,7 +28,7 @@
       isDarkTheme,
       bgStyle: `background: ${colors[theme].bg};`,
       textStyle: `color: ${colors[theme].text};`,
-      darkClass: isDarkTheme ? 'ant-card-dark' : '',
+      darkClass: isDarkTheme ? 'ant-dark' : '',
       darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
       darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
       get currentTheme() {
@@ -40,7 +40,7 @@
         localStorage.setItem('dark-mode', this.isDarkTheme);
         this.bgStyle = `background: ${colors[this.theme].bg};`;
         this.textStyle = `color: ${colors[this.theme].text};`;
-        this.darkClass = this.isDarkTheme ? 'ant-card-dark' : '';
+        this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
         this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
         this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
       },

+ 2 - 2
web/html/xui/form/client.html

@@ -11,10 +11,10 @@
     <br>
     <a-form-item>
         <span slot="label">
-            <span>{{ i18n "pages.inbounds.Email" }}</span>
+            <span>{{ i18n "pages.inbounds.email" }}</span>
             <a-tooltip>
                 <template slot="title">
-                    <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
+                    <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
                 </template>
                 <a-icon type="sync" @click="getNewEmail(client)"></a-icon>
             </a-tooltip>

+ 2 - 2
web/html/xui/form/protocol/shadowsocks.html

@@ -4,10 +4,10 @@
         <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
             <a-form-item>
                 <span slot="label">
-                    <span>{{ i18n "pages.inbounds.Email" }}</span>
+                    <span>{{ i18n "pages.inbounds.email" }}</span>
                     <a-tooltip>
                         <template slot="title">
-                            <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
+                            <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
                         </template>
                         <a-icon @click="getNewEmail(client)" type="sync"></a-icon>
                     </a-tooltip>

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

@@ -4,10 +4,10 @@
         <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
             <a-form-item>
                 <span slot="label">
-                    <span>{{ i18n "pages.inbounds.Email" }}</span>
+                    <span>{{ i18n "pages.inbounds.email" }}</span>
                     <a-tooltip>
                         <template slot="title">
-                            <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
+                            <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
                         </template>
                         <a-icon type="sync" @click="getNewEmail(client)"></a-icon>
                     </a-tooltip>

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

@@ -4,10 +4,10 @@
         <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
             <a-form-item>
                 <span slot="label">
-                    <span>{{ i18n "pages.inbounds.Email" }}</span>
+                    <span>{{ i18n "pages.inbounds.email" }}</span>
                     <a-tooltip>
                         <template slot="title">
-                            <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
+                            <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
                         </template>
                         <a-icon type="sync" @click="getNewEmail(client)"></a-icon>
                     </a-tooltip>

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

@@ -4,10 +4,10 @@
         <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
             <a-form-item>
                 <span slot="label">
-                    <span>{{ i18n "pages.inbounds.Email" }}</span>
+                    <span>{{ i18n "pages.inbounds.email" }}</span>
                     <a-tooltip>
                         <template slot="title">
-                            <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
+                            <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
                         </template>
                         <a-icon type="sync" @click="getNewEmail(client)"></a-icon>
                     </a-tooltip>

+ 3 - 10
web/html/xui/inbound_info_modal.html

@@ -114,6 +114,7 @@
         <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>
@@ -190,7 +191,7 @@
     <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"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
+        <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>
     </div>
 </a-modal>
 <script>
@@ -218,14 +219,6 @@
             this.isExpired = this.inbound.isExpiry(index);
             this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
             this.visible = true;
-            infoModalApp.$nextTick(() => {
-                if (this.clipboard === null) {
-                    this.clipboard = new ClipboardJS('#copy-url-link', {
-                        text: () => this.link,
-                    });
-                    this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
-                }
-            });
         },
         close() {
             infoModal.visible = false;
@@ -263,7 +256,7 @@
             },
         },
         methods: {
-            copyTextToClipboard(elmentId, content) {
+            copyToClipboard(elmentId, content) {
                 this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
                     text: () => content,
                 });

+ 16 - 15
web/html/xui/inbounds.html

@@ -338,7 +338,7 @@
             },
             async getDBInbounds() {
                 this.refreshing = true;
-                const msg = await HttpUtil.post('/xui/inbound/list');
+                const msg = await HttpUtil.post('/panel/inbound/list');
                 if (!msg.success) {
                     return;
                 }
@@ -346,7 +346,7 @@
                 this.refreshing = false;
             },
             async getDefaultSettings() {
-                const msg = await HttpUtil.post('/xui/setting/defaultSettings');
+                const msg = await HttpUtil.post('/panel/setting/defaultSettings');
                 if (!msg.success) {
                     return;
                 }
@@ -509,7 +509,7 @@
                     streamSettings: baseInbound.stream.toString(),
                     sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
                 };
-                await this.submit('/xui/inbound/add', data, inModal);
+                await this.submit('/panel/inbound/add', data, inModal);
             },
             openAddInbound() {
                 inModal.show({
@@ -558,7 +558,7 @@
                 if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
                 if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
 
-                await this.submit('/xui/inbound/add', data, inModal);
+                await this.submit('/panel/inbound/add', data, inModal);
             },
             async updateInbound(inbound, dbInbound) {
                 const data = {
@@ -577,7 +577,7 @@
                 if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
                 if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
 
-                await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
+                await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
             },
             openAddClient(dbInboundId) {
                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -632,14 +632,14 @@
                     id: dbInboundId,
                     settings: '{"clients": [' + clients.toString() + ']}',
                 };
-                await this.submit(`/xui/inbound/addClient`, data);
+                await this.submit(`/panel/inbound/addClient`, data);
             },
             async updateClient(client, dbInboundId, clientId) {
                 const data = {
                     id: dbInboundId,
                     settings: '{"clients": [' + client.toString() + ']}',
                 };
-                await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
+                await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
             },
             resetTraffic(dbInboundId) {
                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -664,7 +664,7 @@
                     class: themeSwitcher.darkCardClass,
                     okText: '{{ i18n "delete"}}',
                     cancelText: '{{ i18n "cancel"}}',
-                    onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
+                    onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
                 });
             },
             delClient(dbInboundId, client) {
@@ -676,7 +676,7 @@
                     class: themeSwitcher.darkCardClass,
                     okText: '{{ i18n "delete"}}',
                     cancelText: '{{ i18n "cancel"}}',
-                    onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
+                    onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
                 });
             },
             getClients(protocol, clientSettings) {
@@ -696,15 +696,16 @@
                 }
             },
             showQrcode(dbInbound, clientIndex) {
+                const clientName = JSON.parse(dbInbound.settings).clients[clientIndex].email;
                 const link = dbInbound.genLink(clientIndex);
-                qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
+                qrModal.show('{{ i18n "qrCode"}}', link, dbInbound, '', clientName);
             },
             showInfo(dbInbound, index) {
                 infoModal.show(dbInbound, index);
             },
             switchEnable(dbInboundId) {
                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
-                this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
+                this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound);
             },
             async switchEnableClient(dbInboundId, client) {
                 this.loading()
@@ -741,7 +742,7 @@
                     class: themeSwitcher.darkCardClass,
                     okText: '{{ i18n "reset"}}',
                     cancelText: '{{ i18n "cancel"}}',
-                    onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
+                    onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
                 })
             },
             resetAllTraffic() {
@@ -751,7 +752,7 @@
                     class: themeSwitcher.darkCardClass,
                     okText: '{{ i18n "reset"}}',
                     cancelText: '{{ i18n "cancel"}}',
-                    onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
+                    onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
                 });
             },
             resetAllClientTraffics(dbInboundId) {
@@ -761,7 +762,7 @@
                     class: themeSwitcher.darkCardClass,
                     okText: '{{ i18n "reset"}}',
                     cancelText: '{{ i18n "cancel"}}',
-                    onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
+                    onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
                 })
             },
             delDepletedClients(dbInboundId) {
@@ -771,7 +772,7 @@
                     class: themeSwitcher.darkCardClass,
                     okText: '{{ i18n "reset"}}',
                     cancelText: '{{ i18n "cancel"}}',
-                    onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
+                    onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
                 })
             },
             isExpiry(dbInbound, index) {

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

@@ -500,7 +500,7 @@
                             return;
                         }
                         this.loading(true);
-                        const restartMsg = await HttpUtil.post("/xui/setting/restartPanel");
+                        const restartMsg = await HttpUtil.post("/panel/setting/restartPanel");
                         this.loading(false);
                         if (restartMsg.success) {
                             this.loading(true);

+ 50 - 7
web/html/xui/settings.html

@@ -153,6 +153,7 @@
                                                 <setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
                                                 <setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigAds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
                                                 <setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPorn"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPornDesc"}}' v-model="PornSettings"></setting-list-item>
+                                                <setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpeedtest"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpeedtestDesc"}}' v-model="SpeedTestSettings"></setting-list-item>
                                             </a-collapse-panel>
                                             <a-collapse-panel header='{{ i18n "pages.settings.templates.countryConfigs"}}'>
                                                 <a-row :xs="24" :sm="24" :lg="12">
@@ -285,6 +286,7 @@
                         "geosite:spotify-ads"
                     ],
                     porn: ["geosite:category-porn"],
+                    speedtest: ["geosite:speedtest"],
                     openai: ["geosite:openai"],
                     google: ["geosite:google"],
                     spotify: ["geosite:spotify"],
@@ -307,13 +309,16 @@
                 },
             }
         },
+        created() {
+            this.checkForGeosites();
+        },
         methods: {
             loading(spinning = true, obj) {
                 if (obj == null) this.spinning = spinning;
             },
             async getAllSetting() {
                 this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/all");
+                const msg = await HttpUtil.post("/panel/setting/all");
                 this.loading(false);
                 if (msg.success) {
                     this.oldAllSetting = new AllSetting(msg.obj);
@@ -324,7 +329,7 @@
             },
             async updateAllSetting() {
                 this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/update", this.allSetting);
+                const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
                 this.loading(false);
                 if (msg.success) {
                     await this.getAllSetting();
@@ -332,7 +337,7 @@
             },
             async updateUser() {
                 this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/updateUser", this.user);
+                const msg = await HttpUtil.post("/panel/setting/updateUser", this.user);
                 this.loading(false);
                 if (msg.success) {
                     this.user = {};
@@ -350,7 +355,7 @@
                     });
                 });
                 this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/restartPanel");
+                const msg = await HttpUtil.post("/panel/setting/restartPanel");
                 this.loading(false);
                 if (msg.success) {
                     this.loading(true);
@@ -359,7 +364,7 @@
                 }
             },
             async getUserSecret() {
-                const user_msg = await HttpUtil.post("/xui/setting/getUserSecret", this.user);
+                const user_msg = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
                 if (user_msg.success) {
                     this.user = user_msg.obj;
                 }
@@ -367,7 +372,7 @@
             },
             async updateSecret() {
                 this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/updateUserSecret", this.user);
+                const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
                 if (msg.success) {
                     this.user = msg.obj;
                     window.location.replace(basePath + "logout")
@@ -394,13 +399,34 @@
             },
             async resetXrayConfigToDefault() {
                 this.loading(true);
-                const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
+                const msg = await HttpUtil.get("/panel/setting/getDefaultJsonConfig");
                 this.loading(false);
                 if (msg.success) {
                     this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
                     this.saveBtnDisable = true;
                 }
             },
+            checkForGeosites() {
+                const domainsToCheck = [
+                    {
+                        query: "category-ru-gov",
+                        key: "this.settingsData.domains.ru",
+                        data: [
+                            "geosite:category-ru-gov",
+                            "regexp:.*\\.ru$"
+                        ]
+                    },
+                ];
+                this.loading(true);
+                domainsToCheck.forEach(async (dd) => {
+                    const msg = await HttpUtil.get(`/panel/setting/searchDatafiles?query=${dd.query}`);
+                    if (msg.success && msg.obj) {
+                        [dd.key] = dd.data;
+                        console.log([dd.key])
+                    }
+                })
+                this.loading(false);
+            },
             checkRequiredOutbounds() {
                 const newTemplateSettings = this.templateSettings;
                 const haveIPv4Outbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "IPv4");
@@ -573,6 +599,23 @@
                     });
                 },
             },
+            SpeedTestSettings: {
+                get: function () {
+                    return this.templateRuleGetter({
+                        outboundTag: "blocked",
+                        property: "domain",
+                        data: this.settingsData.domains.speedtest
+                    });
+                },
+                set: function (newValue) {
+                    this.templateRuleSetter({
+                        newValue,
+                        outboundTag: "blocked",
+                        property: "domain",
+                        data: this.settingsData.domains.speedtest
+                    });
+                },
+            },
             GoogleIPv4Settings: {
                 get: function () {
                     return this.templateRuleGetter({

+ 2 - 2
web/service/server.go

@@ -179,7 +179,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
 }
 
 func (s *ServerService) GetXrayVersions() ([]string, error) {
-	url := "https://api.github.com/repos/mhsanaei/Xray-core/releases"
+	url := "https://api.github.com/repos/MHSanaei/Xray-core/releases"
 	resp, err := http.Get(url)
 	if err != nil {
 		return nil, err
@@ -246,7 +246,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
 	}
 
 	fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
-	url := fmt.Sprintf("https://github.com/mhsanaei/Xray-core/releases/download/%s/%s", version, fileName)
+	url := fmt.Sprintf("https://github.com/MHSanaei/Xray-core/releases/download/%s/%s", version, fileName)
 	resp, err := http.Get(url)
 	if err != nil {
 		return "", err

+ 27 - 0
web/service/setting.go

@@ -1,10 +1,12 @@
 package service
 
 import (
+	"bufio"
 	_ "embed"
 	"encoding/json"
 	"errors"
 	"fmt"
+	"os"
 	"reflect"
 	"strconv"
 	"strings"
@@ -16,6 +18,7 @@ import (
 	"x-ui/util/random"
 	"x-ui/util/reflect_util"
 	"x-ui/web/entity"
+	"x-ui/xray"
 )
 
 //go:embed config.json
@@ -351,3 +354,27 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
 	}
 	return common.Combine(errs...)
 }
+
+func (s *SettingService) SearchDatafiles(query string) (bool, error) {
+	// Open the file for reading
+	file, err := os.Open(xray.GetGeositePath())
+	if err != nil {
+		return false, common.NewErrorf("Error opening geosite.dat: %v", err)
+	}
+	defer file.Close()
+
+	// Create a scanner to read the file line-by-line
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := scanner.Text()
+		if strings.Contains(strings.ToLower(line), strings.ToLower(query)) {
+			return true, nil
+		}
+	}
+
+	err = scanner.Err()
+	if err != nil {
+		return false, common.NewErrorf("Error while scanning geosite.dat: %v", err)
+	}
+	return false, nil
+}

+ 2 - 2
web/service/sub.go

@@ -38,7 +38,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
 			continue
 		}
 		for _, client := range clients {
-			if client.SubID == subId {
+			if client.Enable && client.SubID == subId {
 				link := s.getLink(inbound, client.Email)
 				result = append(result, link)
 				clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
@@ -73,7 +73,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
 func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
 	db := database.GetDB()
 	var inbounds []*model.Inbound
-	err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
+	err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
 	if err != nil && err != gorm.ErrRecordNotFound {
 		return nil, err
 	}

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

@@ -150,8 +150,6 @@
 "resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
 "resetAllTrafficOkText" = "Confirm"
 "resetAllTrafficCancelText" = "Cancel"
-"IPLimit" = "IP Limit"
-"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)."
 "resetInboundClientTraffics" = "Reset Clients Traffic"
 "resetInboundClientTrafficTitle" = "Reset all client traffic"
 "resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
@@ -161,8 +159,10 @@
 "delDepletedClients" = "Delete Depleted Clients"
 "delDepletedClientsTitle" = "Delete depleted clients"
 "delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?"
-"Email" = "Email"
-"EmailDesc" = "Please provide a unique email address."
+"email" = "Email"
+"emailDesc" = "Please provide a unique email address."
+"IPLimit" = "IP Limit"
+"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)."
 "IPLimitlog" = "IP Log"
 "IPLimitlogDesc" = "IPs history log (before enabling inbound after it has been disabled by IP limit, you should clear the log)."
 "IPLimitlogclear" = "Clear The Log"
@@ -277,6 +277,8 @@
 "xrayConfigAdsDesc" = "Change the configuration template to block ads. Restart the panel to apply changes."
 "xrayConfigPorn" = "Block Porn Websites"
 "xrayConfigPornDesc" = "Change the configuration template to avoid connecting to porn websites. Restart the panel to apply changes."
+"xrayConfigSpeedtest" = "Block Speedtest Websites"
+"xrayConfigSpeedtestDesc" = "Change the configuration template to avoid connecting to speedtest websites. Restart the panel to apply changes."
 "xrayConfigIRIp" = "Disable connection to Iran IP ranges"
 "xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges. Restart the panel to apply changes."
 "xrayConfigIRDomain" = "Disable connection to Iran domains"

+ 4 - 2
web/translation/translate.fa_IR.toml

@@ -157,10 +157,10 @@
 "delDepletedClients" = "حذف کاربران منقضی"
 "delDepletedClientsTitle" = "حذف کاربران منقضی"
 "delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
+"email" = "ایمیل"
+"emailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
 "IPLimit" = "محدودیت ای پی"
 "IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
-"Email" = "ایمیل"
-"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
 "IPLimitlog" = "گزارش ها"
 "IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
 "IPLimitlogclear" = "پاک کردن گزارش ها"
@@ -275,6 +275,8 @@
 "xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
 "xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
 "xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigSpeedtest" = "جلوگیری از اتصال به سایت های تست سرعت"
+"xrayConfigSpeedtestDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های تست سرعت تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
 "xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
 "xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
 "xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"

+ 6 - 4
web/translation/translate.ru_RU.toml

@@ -150,8 +150,6 @@
 "resetAllTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
 "resetAllTrafficOkText" = "Подтвердить"
 "resetAllTrafficCancelText" = "Отмена"
-"IPLimit" = "ограничение по IP"
-"IPLimitDesc" = "Отключить ключ, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)."
 "resetInboundClientTraffics" = "Обнулить траффик пользователей"
 "resetInboundClientTrafficTitle" = "Обнуление траффика пользователей"
 "resetInboundClientTrafficContent" = "Вы уверены, что хотите обнулить весь трафик для этих пользователей?"
@@ -161,8 +159,10 @@
 "delDepletedClients" = "Удалить отключенных пользователей"
 "delDepletedClientsTitle" = "Удаление отключенных пользователей"
 "delDepletedClientsContent" = "Подтверждаете удаление отключенных пользователей?"
-"Email" = "Email"
-"EmailDesc" = "Пожалуйста, укажите уникальный Email"
+"email" = "Email"
+"emailDesc" = "Пожалуйста, укажите уникальный Email"
+"IPLimit" = "ограничение по IP"
+"IPLimitDesc" = "Отключить ключ, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)."
 "IPLimitlog" = "IP лог"
 "IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)."
 "IPLimitlogclear" = "Очистить список"
@@ -277,6 +277,8 @@
 "xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу. Перезагрузите панель для применения настроек."
 "xrayConfigPorn" = "Блокировка порносайтов"
 "xrayConfigPornDesc" = "Измените конфигурацию, чтобы отключить подключения к порносайтам. Перезагрузите панель для применения настроек."
+"xrayConfigSpeedtest" = "Блокировать сайты для проверки скорости"
+"xrayConfigSpeedtestDesc" = "Измените шаблон конфигурации, чтобы избежать подключения к веб-сайтам для тестирования скорости. Перезапустите панель, чтобы применить изменения."
 "xrayConfigIRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
 "xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана. Перезагрузите панель для применения настроек."
 "xrayConfigIRDomain" = "Отключить подключение к доменам Ирана"

+ 4 - 2
web/translation/translate.zh_Hans.toml

@@ -157,10 +157,10 @@
 "delDepletedClients" = "删除耗尽的客户端"
 "delDepletedClientsTitle" = "删除耗尽的客户"
 "delDepletedClientsContent" = "你确定要删除所有耗尽的客户端吗?"
+"email" = "电子邮件"
+"emailDesc" = "电子邮件必须完全唯"
 "IPLimit" = "IP限制"
 "IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
-"Email" = "电子邮件"
-"EmailDesc" = "电子邮件必须完全唯"
 "IPLimitlog" = "IP日志"
 "IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
 "IPLimitlogclear" = "清除日志"
@@ -275,6 +275,8 @@
 "xrayConfigAdsDesc" = "修改配置模板屏蔽广告,重启面板生效"
 "xrayConfigPorn" = "禁止色情网站连接"
 "xrayConfigPornDesc" = "更改配置模板避免连接色情网站,重启面板生效"
+"xrayConfigSpeedtest" = "阻止测速网站"
+"xrayConfigSpeedtestDesc" = "更改配置模板以避免连接到速度测试网站。 重新启动面板以应用更改。"
 "xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
 "xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段,重启面板生效"
 "xrayConfigIRDomain" = "禁止伊朗域连接"

+ 2 - 2
web/web.go

@@ -83,7 +83,7 @@ type Server struct {
 
 	index  *controller.IndexController
 	server *controller.ServerController
-	xui    *controller.XUIController
+	panel  *controller.XUIController
 	api    *controller.APIController
 	sub    *controller.SUBController
 
@@ -207,7 +207,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
 
 	s.index = controller.NewIndexController(g)
 	s.server = controller.NewServerController(g)
-	s.xui = controller.NewXUIController(g)
+	s.panel = controller.NewXUIController(g)
 	s.api = controller.NewAPIController(g)
 	s.sub = controller.NewSUBController(g)