Browse Source

Merge pull request #74 from MHSanaei/dev

Dev
MHSanaei 1 year ago
parent
commit
77be5cf7d8

+ 16 - 5
web/controller/server.go

@@ -1,10 +1,11 @@
 package controller
 
 import (
-	"github.com/gin-gonic/gin"
 	"time"
 	"x-ui/web/global"
 	"x-ui/web/service"
+
+	"github.com/gin-gonic/gin"
 )
 
 type ServerController struct {
@@ -37,6 +38,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
 	g.POST("/stopXrayService", a.stopXrayService)
 	g.POST("/restartXrayService", a.restartXrayService)
 	g.POST("/installXray/:version", a.installXray)
+	g.POST("/logs", a.getLogs)
 }
 
 func (a *ServerController) refreshStatus() {
@@ -87,13 +89,13 @@ func (a *ServerController) installXray(c *gin.Context) {
 }
 
 func (a *ServerController) stopXrayService(c *gin.Context) {
-        a.lastGetStatusTime = time.Now()
+	a.lastGetStatusTime = time.Now()
 	err := a.serverService.StopXrayService()
 	if err != nil {
 		jsonMsg(c, "", err)
 		return
 	}
-	jsonMsg(c, "Xray stoped",err)
+	jsonMsg(c, "Xray stoped", err)
 
 }
 func (a *ServerController) restartXrayService(c *gin.Context) {
@@ -102,6 +104,15 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
 		jsonMsg(c, "", err)
 		return
 	}
-	jsonMsg(c, "Xray restarted",err)
+	jsonMsg(c, "Xray restarted", err)
+
+}
 
-}
+func (a *ServerController) getLogs(c *gin.Context) {
+	logs, err := a.serverService.GetLogs()
+	if err != nil {
+		jsonMsg(c, I18n(c, "getLogs"), err)
+		return
+	}
+	jsonObj(c, logs, nil)
+}

+ 5 - 13
web/html/common/qrcode_modal.html

@@ -1,9 +1,10 @@
 {{define "qrcodeModal"}}
 <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
-         :closable="true" width="300px" :ok-text="qrModal.okText"
+         :closable="true"
          :class="siderDrawer.isDarkTheme ? darkClass : ''"
-         cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}">
-         <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
+         :footer="null"
+         width="300px">
+    <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-modal>
 
@@ -14,17 +15,15 @@
         content: '',
         inbound: new Inbound(),
         dbInbound: new DBInbound(),
-        okText: '',
         copyText: '',
         qrcode: null,
         clipboard: null,
         visible: false,
-        show: function (title='', content='', dbInbound=new DBInbound(),okText='{{ i18n "copy" }}', copyText='') {
+        show: function (title='', content='', dbInbound=new DBInbound(), copyText='') {
             this.title = title;
             this.content = content;
             this.dbInbound = dbInbound;
             this.inbound = dbInbound.toInbound();
-            this.okText = okText;
             if (ObjectUtil.isEmpty(copyText)) {
                 this.copyText = content;
             } else {
@@ -32,13 +31,6 @@
             }
             this.visible = true;
             qrModalApp.$nextTick(() => {
-                this.clipboard = new ClipboardJS('#qr-modal-ok-btn', {
-                    text: () => this.copyText,
-                });
-                this.clipboard.on('success', () => {
-                    app.$message.success('{{ i18n "copied" }}')
-                    this.clipboard.destroy();
-                });
                 if (this.qrcode === null) {
                     this.qrcode = new QRious({
                         element: document.querySelector('#qrCode'),

+ 7 - 5
web/html/xui/client_bulk_modal.html

@@ -7,10 +7,11 @@
         <a-form-item label='{{ i18n "pages.client.method" }}'>
             <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
                 <a-select-option :value="0">Random</a-select-option>
-                <a-select-option :value="1">Random_Prefix</a-select-option>
-                <a-select-option :value="2">Random_Prefix+Num</a-select-option>
-                <a-select-option :value="3">Random_Prefix+Num+Postfix</a-select-option>
-                <a-select-option :value="4">Random_Prefix+Num@Telegram Username</a-select-option>
+                <a-select-option :value="1">Random+Prefix</a-select-option>
+                <a-select-option :value="2">Random+Prefix+Num</a-select-option>
+                <a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
+                <a-select-option :value="4">Random+Prefix+Num@Telegram Username</a-select-option>
+                <a-select-option :value="5">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option>
             </a-select>
         </a-form-item><br />
         <a-form-item v-if="clientsBulkModal.emailMethod>1">
@@ -91,11 +92,12 @@
                 start=0;
                 end=clientsBulkModal.quantity;
             }
-            prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? "_" + clientsBulkModal.emailPrefix : "";
+            prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : "";
             useNum=(method>1);
             postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? (method == 4 ? "@" : "") + clientsBulkModal.emailPostfix : "";
             for (let i = start; i < end; i++) {
                 newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
+                if(method==5) newClient.email = "";
                 newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
                 newClient._totalGB = clientsBulkModal.totalGB;
                 newClient._expiryTime = clientsBulkModal.expiryTime;

+ 5 - 7
web/html/xui/inbound_info_modal.html

@@ -59,13 +59,11 @@
     </table>
     <template v-if="infoModal.clientSettings">
     <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
-    <table style="margin-bottom: 10px; width: 100%;">
-        <tr>
-            <th v-for="col in Object.keys(infoModal.clientSettings).slice(0, 3)">[[ col ]]</th>
-        </tr>
-        <tr>
-            <td v-for="col in Object.values(infoModal.clientSettings).slice(0, 3)"><a-tag color="green">[[ col ]]</a-tag></td>
-    </table>
+	<table style="margin-bottom: 10px;">
+        <tr v-for="col,index in Object.keys(infoModal.clientSettings).slice(0, 3)">
+            <td>[[ col ]]</td>
+            <td><a-tag color="green">[[ infoModal.clientSettings[col] ]]</a-tag></td>    
+	</table>
     <table style="margin-bottom: 10px; width: 100%;">
             <tr><th>{{ i18n "usage" }}</th><th>{{ i18n "pages.inbounds.totalFlow" }}</th><th>{{ i18n "pages.inbounds.expireDate" }}</th><th>{{ i18n "enable" }}</th></tr>    
         <tr>

+ 41 - 6
web/html/xui/index.html

@@ -84,16 +84,18 @@
                                 </template>
                                 <a-icon type="question-circle" theme="filled"></a-icon>
                             </a-tooltip>
-                            <a-tag color="green" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
-                            <a-tag color="blue" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
-                            <a-tag color="blue" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>                    
-                            <a-tag color="blue" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
+                            <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
+                            <a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
+                            <a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>                    
+                            <a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
+							<a-tag color="blue" style="cursor: pointer;" @click="openLogs">Logs</a-tag>
+							<a-tag color="green">3x-ui v{{ .cur_ver }}</a-tag>
                         </a-card>
                     </a-col>
                     <a-col :sm="24" :md="12">
                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
                             {{ i18n "pages.index.operationHours" }}:
-                            <a-tag color="#87d068">[[ formatSecond(status.uptime) ]]</a-tag>
+                            <a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
                             <a-tooltip>
                                 <template slot="title">
                                     {{ i18n "pages.index.operationHoursDesc" }}
@@ -177,7 +179,7 @@
     <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
              :closable="true" @ok="() => versionModal.visible = false"
              :class="siderDrawer.isDarkTheme ? darkClass : ''"
-             ok-text='{{ i18n "confirm" }}' cancel-text='{{ i18n "cancel"}}'>
+             footer="">
         <h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
         <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
         <template v-for="version, index in versionModal.versions">
@@ -187,6 +189,17 @@
             </a-tag>
         </template>
     </a-modal>
+    <a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
+             :closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
+             :class="siderDrawer.isDarkTheme ? darkClass : ''"
+             width="800px"
+             footer="">
+        <table style="margin: 0px; width: 100%; background-color: black; color: hsla(0,0%,100%,.65);">
+            <tr v-for="log , index in logModal.logs">
+                <td style="vertical-align: top;">[[ index ]]</td><td>[[ log ]]</td>
+            </tr>
+        </table>
+    </a-modal>
 </a-layout>
 {{template "js" .}}
 <script>
@@ -280,6 +293,18 @@
         },
     };
 
+    const logModal = {
+        visible: false,
+        logs: '',
+        show(logs) {
+            this.visible = true;
+            this.logs = logs;
+        },
+        hide() {
+            this.visible = false;
+        },
+    };
+
     const app = new Vue({
         delimiters: ['[[', ']]'],
         el: '#app',
@@ -287,6 +312,7 @@
             siderDrawer,
             status: new Status(),
             versionModal,
+            logModal,
             spinning: false,
             loadingTip: '{{ i18n "loading"}}',
         },
@@ -346,6 +372,15 @@
                     return;
                 }
             },
+            async openLogs(){
+                this.loading(true);
+                const msg = await HttpUtil.post('server/logs');
+                this.loading(false);
+                if (!msg.success) {
+                    return;
+                }
+                logModal.show(msg.obj);
+            }
         },
         async mounted() {
             while (true) {

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

@@ -40,7 +40,7 @@
 
                             <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
+                                <setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
@@ -117,7 +117,7 @@
                             <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
                                 <setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}'  v-model="allSetting.tgBotEnable"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.telegramToken"}}' desc='{{ i18n "pages.setting.telegramTokenDesc"}}'  v-model="allSetting.tgBotToken"></setting-list-item>
-                                <setting-list-item type="number" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}'  v-model.number="allSetting.tgBotChatId"></setting-list-item>
+                                <setting-list-item type="text" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}'  v-model="allSetting.tgBotChatId"></setting-list-item>
                                 <setting-list-item type="text" title='{{ i18n "pages.setting.telegramNotifyTime"}}' desc='{{ i18n "pages.setting.telegramNotifyTimeDesc"}}'  v-model="allSetting.tgRunTime"></setting-list-item>
                                 <setting-list-item type="switch" title='{{ i18n "pages.setting.tgNotifyBackup" }}' desc='{{ i18n "pages.setting.tgNotifyBackupDesc" }}'  v-model="allSetting.tgBotBackup"></setting-list-item>
                                 <setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyExpireTimeDiff" }}' desc='{{ i18n "pages.setting.tgNotifyExpireTimeDiffDesc" }}'  v-model="allSetting.tgExpireDiff" :min="0"></setting-list-item>

+ 10 - 0
web/service/inbound.go

@@ -634,3 +634,13 @@ func (s *InboundService) ClearClientIps(clientEmail string) error {
 	}
 	return nil
 }
+
+func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error) {
+	db := database.GetDB()
+	var inbounds []*model.Inbound
+	err := db.Model(model.Inbound{}).Preload("ClientStats").Where("remark like ?", "%"+query+"%").Find(&inbounds).Error
+	if err != nil && err != gorm.ErrRecordNotFound {
+		return nil, err
+	}
+	return inbounds, nil
+}

+ 36 - 11
web/service/server.go

@@ -9,7 +9,9 @@ import (
 	"io/fs"
 	"net/http"
 	"os"
+	"os/exec"
 	"runtime"
+	"strings"
 	"time"
 	"x-ui/logger"
 	"x-ui/util/sys"
@@ -200,24 +202,24 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
 
 func (s *ServerService) StopXrayService() (string error) {
 
-        err := s.xrayService.StopXray()
-                if err != nil {
-                        logger.Error("stop xray failed:", err)
-                        return err
-                }
+	err := s.xrayService.StopXray()
+	if err != nil {
+		logger.Error("stop xray failed:", err)
+		return err
+	}
 
 	return nil
 }
 
 func (s *ServerService) RestartXrayService() (string error) {
 
-        s.xrayService.StopXray()
-        defer func() {
-                err := s.xrayService.RestartXray(true)
-                if err != nil {
-                        logger.Error("start xray failed:", err)
+	s.xrayService.StopXray()
+	defer func() {
+		err := s.xrayService.RestartXray(true)
+		if err != nil {
+			logger.Error("start xray failed:", err)
 		}
-        }()
+	}()
 
 	return nil
 }
@@ -324,3 +326,26 @@ func (s *ServerService) UpdateXray(version string) error {
 	return nil
 
 }
+
+func (s *ServerService) GetLogs() ([]string, error) {
+	// Define the journalctl command and its arguments
+	var cmdArgs []string
+	if runtime.GOOS == "linux" {
+		cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", "100"}
+	} else {
+		return []string{"Unsupported operating system"}, nil
+	}
+
+	// Run the command
+	cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
+	var out bytes.Buffer
+	cmd.Stdout = &out
+	err := cmd.Run()
+	if err != nil {
+		return nil, err
+	}
+
+	lines := strings.Split(out.String(), "\n")
+
+	return lines, nil
+}

+ 59 - 13
web/service/tgbot.go

@@ -105,8 +105,6 @@ func (t *Tgbot) OnReceive() {
 		} else {
 			if update.Message.IsCommand() {
 				t.answerCommand(update.Message, chatId, isAdmin)
-			} else {
-				t.aswerChat(update.Message.Text, chatId, isAdmin)
 			}
 		}
 	}
@@ -137,16 +135,18 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
 		} else {
 			msg = "❗Please provide a text for search!"
 		}
+	case "inbound":
+		if isAdmin {
+			t.searchInbound(chatId, message.CommandArguments())
+		} else {
+			msg = "❗ Unknown command"
+		}
 	default:
 		msg = "❗ Unknown command"
 	}
 	t.SendAnswer(chatId, msg, isAdmin)
 }
 
-func (t *Tgbot) aswerChat(message string, chatId int64, isAdmin bool) {
-	t.SendAnswer(chatId, "❗ Unknown message", isAdmin)
-}
-
 func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
 	// Respond to the callback query, telling Telegram to show the user
 	// a message with the data received.
@@ -169,7 +169,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
 	case "client_commands":
 		t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess and vless and Password for Trojan.")
 	case "commands":
-		t.SendMsgToTgbot(callbackQuery.From.ID, "To search for a client email, just use folowing command:\r\n \r\n<code>/usage email</code>")
+		t.SendMsgToTgbot(callbackQuery.From.ID, "Search for a client email:\r\n<code>/usage email</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [remark]</code>")
 	}
 }
 
@@ -276,6 +276,7 @@ func (t *Tgbot) getServerUsage() string {
 		name = ""
 	}
 	info = fmt.Sprintf("💻 Hostname: %s\r\n", name)
+	info += fmt.Sprintf("🚀X-UI Version: %s\r\n", config.GetVersion())
 	//get ip address
 	var ip string
 	var ipv6 string
@@ -427,6 +428,45 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
 	}
 }
 
+func (t *Tgbot) searchInbound(chatId int64, remark string) {
+	inbouds, err := t.inboundService.SearchInbounds(remark)
+	if err != nil {
+		logger.Warning(err)
+		msg := "❌ Something went wrong!"
+		t.SendMsgToTgbot(chatId, msg)
+		return
+	}
+	for _, inbound := range inbouds {
+		info := ""
+		info += fmt.Sprintf("📍Inbound:%s\r\nPort:%d\r\n", inbound.Remark, inbound.Port)
+		info += fmt.Sprintf("Traffic: %s (↑%s,↓%s)\r\n", common.FormatTraffic((inbound.Up + inbound.Down)), common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down))
+		if inbound.ExpiryTime == 0 {
+			info += "Expire date: ♾ Unlimited\r\n \r\n"
+		} else {
+			info += fmt.Sprintf("Expire date:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
+		}
+		t.SendMsgToTgbot(chatId, info)
+		for _, traffic := range inbound.ClientStats {
+			expiryTime := ""
+			if traffic.ExpiryTime == 0 {
+				expiryTime = "♾Unlimited"
+			} else {
+				expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
+			}
+			total := ""
+			if traffic.Total == 0 {
+				total = "♾Unlimited"
+			} else {
+				total = common.FormatTraffic((traffic.Total))
+			}
+			output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
+				traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
+				total, expiryTime)
+			t.SendMsgToTgbot(chatId, output)
+		}
+	}
+}
+
 func (t *Tgbot) searchForClient(chatId int64, query string) {
 	traffic, err := t.inboundService.SearchClientTraffic(query)
 	if err != nil {
@@ -473,7 +513,7 @@ func (t *Tgbot) getExhausted() string {
 	}
 	ExpireThreshold, err := t.settingService.GetTgExpireDiff()
 	if err == nil && ExpireThreshold > 0 {
-		exDiff = int64(ExpireThreshold) * 84600
+		exDiff = int64(ExpireThreshold) * 84600000
 	}
 	inbounds, err := t.inboundService.GetAllInbounds()
 	if err != nil {
@@ -481,14 +521,14 @@ func (t *Tgbot) getExhausted() string {
 	}
 	for _, inbound := range inbounds {
 		if inbound.Enable {
-			if (inbound.ExpiryTime > 0 && (now-inbound.ExpiryTime < exDiff)) ||
+			if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) ||
 				(inbound.Total > 0 && (inbound.Total-inbound.Up+inbound.Down < trDiff)) {
 				exhaustedInbounds = append(exhaustedInbounds, *inbound)
 			}
 			if len(inbound.ClientStats) > 0 {
 				for _, client := range inbound.ClientStats {
 					if client.Enable {
-						if (client.ExpiryTime > 0 && (now-client.ExpiryTime < exDiff)) ||
+						if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) ||
 							(client.Total > 0 && (client.Total-client.Up+client.Down < trDiff)) {
 							exhaustedClients = append(exhaustedClients, client)
 						}
@@ -502,7 +542,7 @@ func (t *Tgbot) getExhausted() string {
 		}
 	}
 	output += fmt.Sprintf("Exhausted Inbounds count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledInbounds), len(exhaustedInbounds))
-	if len(disabledInbounds)+len(exhaustedInbounds) > 0 {
+	if len(exhaustedInbounds) > 0 {
 		output += "Exhausted Inbounds:\r\n"
 		for _, inbound := range exhaustedInbounds {
 			output += fmt.Sprintf("📍Inbound:%s\r\nPort:%d\r\nTraffic: %s (↑%s,↓%s)\r\n", inbound.Remark, inbound.Port, common.FormatTraffic((inbound.Up + inbound.Down)), common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down))
@@ -514,7 +554,7 @@ func (t *Tgbot) getExhausted() string {
 		}
 	}
 	output += fmt.Sprintf("Exhausted Clients count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledClients), len(exhaustedClients))
-	if len(disabledClients)+len(exhaustedClients) > 0 {
+	if len(exhaustedClients) > 0 {
 		output += "Exhausted Clients:\r\n"
 		for _, traffic := range exhaustedClients {
 			expiryTime := ""
@@ -529,7 +569,7 @@ func (t *Tgbot) getExhausted() string {
 			} else {
 				total = common.FormatTraffic((traffic.Total))
 			}
-			output += fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
+			output += fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire date: %s\r\n \r\n",
 				traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
 				total, expiryTime)
 		}
@@ -547,4 +587,10 @@ func (t *Tgbot) sendBackup(chatId int64) {
 	if err != nil {
 		logger.Warning("Error in uploading backup: ", err)
 	}
+	file = tgbotapi.FilePath(xray.GetConfigPath())
+	msg = tgbotapi.NewDocument(chatId, file)
+	_, err = bot.Send(msg)
+	if err != nil {
+		logger.Warning("Error in uploading config.json: ", err)
+	}
 }