mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-14 05:23:09 +00:00
Merge pull request #324 from hamid-gh98/main
[tgbot] Multi language + More...
This commit is contained in:
16
.gitignore
vendored
16
.gitignore
vendored
@@ -1,15 +1,15 @@
|
||||
.idea
|
||||
.vscode
|
||||
.cache
|
||||
.sync*
|
||||
*.tar.gz
|
||||
access.log
|
||||
error.log
|
||||
tmp
|
||||
main
|
||||
backup/
|
||||
bin/
|
||||
dist/
|
||||
x-ui-*.tar.gz
|
||||
/x-ui
|
||||
/release.sh
|
||||
.sync*
|
||||
main
|
||||
release/
|
||||
access.log
|
||||
error.log
|
||||
.cache
|
||||
/release.sh
|
||||
/x-ui
|
||||
|
||||
@@ -120,6 +120,7 @@ docker build -t x-ui .
|
||||
| :----: | ------------------------------- | ----------------------------------------- |
|
||||
| `GET` | `"/"` | Get all inbounds |
|
||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
||||
| `POST` | `"/add"` | Add inbound |
|
||||
| `POST` | `"/del/:id"` | Delete Inbound |
|
||||
| `POST` | `"/update/:id"` | Update Inbound |
|
||||
|
||||
@@ -197,7 +197,11 @@ body {
|
||||
.ant-layout-sider-zero-width-trigger,
|
||||
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
||||
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
||||
background:#161b22
|
||||
background:#1a212a
|
||||
}
|
||||
|
||||
.ant-tabs:not(.ant-card-dark) {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.ant-card-dark {
|
||||
|
||||
@@ -179,6 +179,7 @@ class AllSetting {
|
||||
this.tgRunTime = "@daily";
|
||||
this.tgBotBackup = false;
|
||||
this.tgCpu = "";
|
||||
this.tgLang = "";
|
||||
this.xrayTemplateConfig = "";
|
||||
this.subEnable = false;
|
||||
this.subListen = "";
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package controller
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"x-ui/web/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type APIController struct {
|
||||
BaseController
|
||||
inboundController *InboundController
|
||||
Tgbot service.Tgbot
|
||||
}
|
||||
|
||||
func NewAPIController(g *gin.RouterGroup) *APIController {
|
||||
@@ -30,6 +35,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||
g.GET("/createbackup", a.createBackup)
|
||||
|
||||
a.inboundController = NewInboundController(g)
|
||||
}
|
||||
@@ -37,39 +43,55 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||
func (a *APIController) inbounds(c *gin.Context) {
|
||||
a.inboundController.getInbounds(c)
|
||||
}
|
||||
|
||||
func (a *APIController) inbound(c *gin.Context) {
|
||||
a.inboundController.getInbound(c)
|
||||
}
|
||||
|
||||
func (a *APIController) getClientTraffics(c *gin.Context) {
|
||||
a.inboundController.getClientTraffics(c)
|
||||
}
|
||||
|
||||
func (a *APIController) addInbound(c *gin.Context) {
|
||||
a.inboundController.addInbound(c)
|
||||
}
|
||||
|
||||
func (a *APIController) delInbound(c *gin.Context) {
|
||||
a.inboundController.delInbound(c)
|
||||
}
|
||||
|
||||
func (a *APIController) updateInbound(c *gin.Context) {
|
||||
a.inboundController.updateInbound(c)
|
||||
}
|
||||
|
||||
func (a *APIController) addInboundClient(c *gin.Context) {
|
||||
a.inboundController.addInboundClient(c)
|
||||
}
|
||||
|
||||
func (a *APIController) delInboundClient(c *gin.Context) {
|
||||
a.inboundController.delInboundClient(c)
|
||||
}
|
||||
|
||||
func (a *APIController) updateInboundClient(c *gin.Context) {
|
||||
a.inboundController.updateInboundClient(c)
|
||||
}
|
||||
|
||||
func (a *APIController) resetClientTraffic(c *gin.Context) {
|
||||
a.inboundController.resetClientTraffic(c)
|
||||
}
|
||||
|
||||
func (a *APIController) resetAllTraffics(c *gin.Context) {
|
||||
a.inboundController.resetAllTraffics(c)
|
||||
}
|
||||
|
||||
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
||||
a.inboundController.resetAllClientTraffics(c)
|
||||
}
|
||||
|
||||
func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||
a.inboundController.delDepletedClients(c)
|
||||
}
|
||||
|
||||
func (a *APIController) createBackup(c *gin.Context) {
|
||||
a.Tgbot.SendBackupToAdmins()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/locale"
|
||||
"x-ui/web/session"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BaseController struct {
|
||||
@@ -12,7 +15,7 @@ type BaseController struct {
|
||||
func (a *BaseController) checkLogin(c *gin.Context) {
|
||||
if !session.IsLogin(c) {
|
||||
if isAjax(c) {
|
||||
pureJsonMsg(c, false, I18n(c, "pages.login.loginAgain"))
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain"))
|
||||
} else {
|
||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||
}
|
||||
@@ -22,11 +25,13 @@ func (a *BaseController) checkLogin(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func I18n(c *gin.Context, name string) string {
|
||||
anyfunc, _ := c.Get("I18n")
|
||||
i18n, _ := anyfunc.(func(key string, params ...string) (string, error))
|
||||
|
||||
message, _ := i18n(name)
|
||||
|
||||
return message
|
||||
func I18nWeb(c *gin.Context, name string, params ...string) string {
|
||||
anyfunc, funcExists := c.Get("I18n")
|
||||
if !funcExists {
|
||||
logger.Warning("I18n function not exists in gin context!")
|
||||
return ""
|
||||
}
|
||||
i18nFunc, _ := anyfunc.(func(i18nType locale.I18nType, key string, keyParams ...string) string)
|
||||
msg := i18nFunc(locale.Web, name, params...)
|
||||
return msg
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
||||
user := session.GetLoginUser(c)
|
||||
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, inbounds, nil)
|
||||
@@ -66,12 +66,12 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
||||
func (a *InboundController) getInbound(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "get"), err)
|
||||
jsonMsg(c, I18nWeb(c, "get"), err)
|
||||
return
|
||||
}
|
||||
inbound, err := a.inboundService.GetInbound(id)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, inbound, nil)
|
||||
@@ -90,7 +90,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||
inbound := &model.Inbound{}
|
||||
err := c.ShouldBind(inbound)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.create"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.create"), err)
|
||||
return
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
@@ -98,7 +98,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||
inbound.Enable = true
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
inbound, err = a.inboundService.AddInbound(inbound)
|
||||
jsonMsgObj(c, I18n(c, "pages.inbounds.create"), inbound, err)
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||
if err == nil {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
@@ -107,11 +107,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||
func (a *InboundController) delInbound(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "delete"), err)
|
||||
jsonMsg(c, I18nWeb(c, "delete"), err)
|
||||
return
|
||||
}
|
||||
err = a.inboundService.DelInbound(id)
|
||||
jsonMsgObj(c, I18n(c, "delete"), id, err)
|
||||
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
||||
if err == nil {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
@@ -120,7 +120,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
||||
func (a *InboundController) updateInbound(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
inbound := &model.Inbound{
|
||||
@@ -128,11 +128,11 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||
}
|
||||
err = c.ShouldBind(inbound)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
inbound, err = a.inboundService.UpdateInbound(inbound)
|
||||
jsonMsgObj(c, I18n(c, "pages.inbounds.update"), inbound, err)
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
|
||||
if err == nil {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
data := &model.Inbound{}
|
||||
err := c.ShouldBind(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
clientId := c.Param("clientId")
|
||||
@@ -182,7 +182,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
inbound := &model.Inbound{}
|
||||
err := c.ShouldBind(inbound)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
email := c.Param("email")
|
||||
@@ -228,7 +228,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
err = a.inboundService.DelDepletedClients(id)
|
||||
|
||||
@@ -47,26 +47,27 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
var form LoginForm
|
||||
err := c.ShouldBind(&form)
|
||||
if err != nil {
|
||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.invalidFormData"))
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||
return
|
||||
}
|
||||
if form.Username == "" {
|
||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyUsername"))
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
||||
return
|
||||
}
|
||||
if form.Password == "" {
|
||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword"))
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
||||
return
|
||||
}
|
||||
|
||||
user := a.userService.CheckUser(form.Username, form.Password)
|
||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||
if user == nil {
|
||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||
return
|
||||
} else {
|
||||
logger.Infof("%s login success,Ip Address:%s\n", form.Username, getRemoteIp(c))
|
||||
logger.Infof("%s login success ,Ip Address: %s\n", form.Username, getRemoteIp(c))
|
||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
|
||||
err = session.SetLoginUser(c, user)
|
||||
logger.Info("user", user.Id, "login success")
|
||||
jsonMsg(c, I18n(c, "pages.login.toasts.successLogin"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
||||
}
|
||||
|
||||
func (a *IndexController) logout(c *gin.Context) {
|
||||
|
||||
@@ -81,7 +81,7 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
|
||||
|
||||
versions, err := a.serverService.GetXrayVersions()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "getVersion"), err)
|
||||
jsonMsg(c, I18nWeb(c, "getVersion"), err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
|
||||
func (a *ServerController) installXray(c *gin.Context) {
|
||||
version := c.Param("version")
|
||||
err := a.serverService.UpdateXray(version)
|
||||
jsonMsg(c, I18n(c, "install")+" xray", err)
|
||||
jsonMsg(c, I18nWeb(c, "install")+" xray", err)
|
||||
}
|
||||
|
||||
func (a *ServerController) stopXrayService(c *gin.Context) {
|
||||
|
||||
@@ -43,7 +43,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
allSetting, err := a.settingService.GetAllSetting()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, allSetting, nil)
|
||||
@@ -52,57 +52,57 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||
expireDiff, err := a.settingService.GetExpireDiff()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
trafficDiff, err := a.settingService.GetTrafficDiff()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
defaultCert, err := a.settingService.GetCertFile()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
defaultKey, err := a.settingService.GetKeyFile()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
tgBotEnable, err := a.settingService.GetTgbotenabled()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
subEnable, err := a.settingService.GetSubEnable()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
subPort, err := a.settingService.GetSubPort()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
subPath, err := a.settingService.GetSubPath()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
subDomain, err := a.settingService.GetSubDomain()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
subKeyFile, err := a.settingService.GetSubKeyFile()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
subCertFile, err := a.settingService.GetSubCertFile()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
subTLS := false
|
||||
@@ -128,27 +128,27 @@ func (a *SettingController) updateSetting(c *gin.Context) {
|
||||
allSetting := &entity.AllSetting{}
|
||||
err := c.ShouldBind(allSetting)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
err = a.settingService.UpdateAllSetting(allSetting)
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) updateUser(c *gin.Context) {
|
||||
form := &updateUserForm{}
|
||||
err := c.ShouldBind(form)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
if user.Username != form.OldUsername || user.Password != form.OldPassword {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), errors.New(I18n(c, "pages.settings.toasts.originalUserPassIncorrect")))
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), errors.New(I18nWeb(c, "pages.settings.toasts.originalUserPassIncorrect")))
|
||||
return
|
||||
}
|
||||
if form.NewUsername == "" || form.NewPassword == "" {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), errors.New(I18n(c, "pages.settings.toasts.userPassMustBeNotEmpty")))
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), errors.New(I18nWeb(c, "pages.settings.toasts.userPassMustBeNotEmpty")))
|
||||
return
|
||||
}
|
||||
err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
|
||||
@@ -157,18 +157,18 @@ func (a *SettingController) updateUser(c *gin.Context) {
|
||||
user.Password = form.NewPassword
|
||||
session.SetLoginUser(c, user)
|
||||
}
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) restartPanel(c *gin.Context) {
|
||||
err := a.panelService.RestartPanel(time.Second * 3)
|
||||
jsonMsg(c, I18n(c, "pages.settings.restartPanel"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.restartPanel"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||
defaultJsonConfig, err := a.settingService.GetDefaultXrayConfig()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, defaultJsonConfig, nil)
|
||||
|
||||
@@ -38,12 +38,12 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
||||
if err == nil {
|
||||
m.Success = true
|
||||
if msg != "" {
|
||||
m.Msg = msg + I18n(c, "success")
|
||||
m.Msg = msg + I18nWeb(c, "success")
|
||||
}
|
||||
} else {
|
||||
m.Success = false
|
||||
m.Msg = msg + I18n(c, "fail") + ": " + err.Error()
|
||||
logger.Warning(msg+I18n(c, "fail")+": ", err)
|
||||
m.Msg = msg + I18nWeb(c, "fail") + ": " + err.Error()
|
||||
logger.Warning(msg+I18nWeb(c, "fail")+": ", err)
|
||||
}
|
||||
c.JSON(http.StatusOK, m)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ type AllSetting struct {
|
||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||
TgLang string `json:"tgLang" form:"tgLang"`
|
||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
</a-row>
|
||||
</div>
|
||||
<a-switch v-model="enableFilter"
|
||||
checked-children="{{ i18n "search" }}" un-checked-children="{{ i18n "filter" }}"
|
||||
@change="toggleFilter">
|
||||
checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
|
||||
@change="toggleFilter" style="margin-right: 10px;">
|
||||
</a-switch>
|
||||
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
||||
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
||||
|
||||
@@ -19,6 +19,32 @@
|
||||
.ant-list-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-msg {
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
padding: 20px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.alert-msg > i {
|
||||
color: inherit;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.collapse-title {
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
padding: 10px 20px;
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
.collapse-title > i {
|
||||
color: inherit;
|
||||
font-size: 24px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<a-layout id="app" v-cloak>
|
||||
@@ -31,11 +57,11 @@
|
||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||
</a-space>
|
||||
<a-tabs default-active-key="1" :class="themeSwitcher.darkCardClass" :style="!themeSwitcher.isDarkTheme? 'background: white':''">
|
||||
<a-tabs default-active-key="1" :class="themeSwitcher.darkCardClass">
|
||||
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelConfig"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="alert-msg">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.infoDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -97,8 +123,8 @@
|
||||
|
||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.xrayConfiguration"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="alert-msg">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.infoDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -108,8 +134,8 @@
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.generalConfigs"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="collapse-title">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.templates.generalConfigsDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -153,8 +179,8 @@
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockConfigs"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="collapse-title">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.templates.blockConfigsDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -165,8 +191,8 @@
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockCountryConfigs"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="collapse-title">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.templates.blockCountryConfigsDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -179,8 +205,8 @@
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.directCountryConfigs"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="collapse-title">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.templates.directCountryConfigsDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -193,8 +219,8 @@
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.ipv4Configs"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="collapse-title">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.templates.ipv4ConfigsDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -203,8 +229,8 @@
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualLists"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="collapse-title">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -212,6 +238,7 @@
|
||||
<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>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||
<a-space direction="horizontal" style="padding: 0 20px">
|
||||
@@ -241,8 +268,8 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="alert-msg">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.infoDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -253,12 +280,35 @@
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramNotifyTime"}}' desc='{{ i18n "pages.settings.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title="Telegram Bot Language" />
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select
|
||||
ref="selectBotLang"
|
||||
v-model="allSetting.tgLang"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||
<span v-text="l.name"></span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||
<h2 class="alert-msg">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.infoDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
@@ -400,7 +450,12 @@
|
||||
if (msg.success) {
|
||||
this.loading(true);
|
||||
await PromiseUtil.sleep(5000);
|
||||
window.location.replace(this.allSetting.webBasePath + "xui/settings");
|
||||
let protocol = "http://";
|
||||
if (this.allSetting.webCertFile !== "") {
|
||||
protocol = "https://";
|
||||
}
|
||||
const { host, pathname } = window.location;
|
||||
window.location.replace(protocol + host + this.allSetting.webBasePath + pathname.slice(1));
|
||||
}
|
||||
},
|
||||
async resetXrayConfigToDefault() {
|
||||
@@ -491,30 +546,30 @@
|
||||
computed: {
|
||||
templateSettings: {
|
||||
get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; },
|
||||
set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2) },
|
||||
set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2); },
|
||||
},
|
||||
inboundSettings: {
|
||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.inbounds = JSON.parse(newValue)
|
||||
this.templateSettings = newTemplateSettings
|
||||
newTemplateSettings.inbounds = JSON.parse(newValue);
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
},
|
||||
outboundSettings: {
|
||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.outbounds = JSON.parse(newValue)
|
||||
this.templateSettings = newTemplateSettings
|
||||
newTemplateSettings.outbounds = JSON.parse(newValue);
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
},
|
||||
routingRuleSettings: {
|
||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.routing.rules = JSON.parse(newValue)
|
||||
this.templateSettings = newTemplateSettings
|
||||
newTemplateSettings.routing.rules = JSON.parse(newValue);
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
},
|
||||
freedomStrategy: {
|
||||
@@ -589,6 +644,15 @@
|
||||
this.syncRulesWithOutbound("direct", this.directSettings);
|
||||
}
|
||||
},
|
||||
ipv4Domains: {
|
||||
get: function () {
|
||||
return this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.templateRuleSetter({ outboundTag: "IPv4", property: "domain", data: newValue });
|
||||
this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
|
||||
}
|
||||
},
|
||||
manualBlockedIPs: {
|
||||
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
|
||||
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
|
||||
@@ -605,6 +669,10 @@
|
||||
get: function () { return JSON.stringify(this.directDomains, null, 2); },
|
||||
set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
|
||||
},
|
||||
manualIPv4Domains: {
|
||||
get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
|
||||
set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
|
||||
},
|
||||
torrentSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
|
||||
@@ -658,40 +726,26 @@
|
||||
},
|
||||
GoogleIPv4Settings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.google, this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" }));
|
||||
return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
oldData = this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
||||
if (newValue) {
|
||||
oldData = [...oldData, ...this.settingsData.domains.google];
|
||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
|
||||
} else {
|
||||
oldData = oldData.filter(data => !this.settingsData.domains.google.includes(data))
|
||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
|
||||
}
|
||||
this.templateRuleSetter({
|
||||
outboundTag: "IPv4",
|
||||
property: "domain",
|
||||
data: oldData
|
||||
});
|
||||
this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
|
||||
},
|
||||
},
|
||||
NetflixIPv4Settings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.netflix, this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" }));
|
||||
return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
oldData = this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
||||
if (newValue) {
|
||||
oldData = [...oldData, ...this.settingsData.domains.netflix];
|
||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
|
||||
} else {
|
||||
oldData = oldData.filter(data => !this.settingsData.domains.netflix.includes(data))
|
||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
||||
}
|
||||
this.templateRuleSetter({
|
||||
outboundTag: "IPv4",
|
||||
property: "domain",
|
||||
data: oldData
|
||||
});
|
||||
this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
|
||||
},
|
||||
},
|
||||
IRIpSettings: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
"x-ui/web/service"
|
||||
|
||||
@@ -24,7 +24,10 @@ func (j *CheckCpuJob) Run() {
|
||||
// get latest status of server
|
||||
percent, err := cpu.Percent(1*time.Second, false)
|
||||
if err == nil && percent[0] > float64(threshold) {
|
||||
msg := fmt.Sprintf("🔴 CPU usage %.2f%% is more than threshold %d%%", percent[0], threshold)
|
||||
msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold",
|
||||
"Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64),
|
||||
"Threshold=="+strconv.Itoa(threshold))
|
||||
|
||||
j.tgbotService.SendMsgToTgbotAdmins(msg)
|
||||
}
|
||||
}
|
||||
|
||||
144
web/locale/locale.go
Normal file
144
web/locale/locale.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package locale
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"x-ui/logger"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var i18nBundle *i18n.Bundle
|
||||
var LocalizerWeb *i18n.Localizer
|
||||
var LocalizerBot *i18n.Localizer
|
||||
|
||||
type I18nType string
|
||||
|
||||
const (
|
||||
Bot I18nType = "bot"
|
||||
Web I18nType = "web"
|
||||
)
|
||||
|
||||
type SettingService interface {
|
||||
GetTgLang() (string, error)
|
||||
}
|
||||
|
||||
func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
||||
// set default bundle to english
|
||||
i18nBundle = i18n.NewBundle(language.English)
|
||||
i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||
|
||||
// parse files
|
||||
if err := parseTranslationFiles(i18nFS, i18nBundle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// setup bot locale
|
||||
if err := initTGBotLocalizer(settingService); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
|
||||
var sep string = "=="
|
||||
if len(seperator) > 0 {
|
||||
sep = seperator[0]
|
||||
}
|
||||
|
||||
templateData := make(map[string]interface{})
|
||||
for _, param := range params {
|
||||
parts := strings.SplitN(param, sep, 2)
|
||||
templateData[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
return templateData
|
||||
}
|
||||
|
||||
func I18n(i18nType I18nType, key string, params ...string) string {
|
||||
var localizer *i18n.Localizer
|
||||
|
||||
switch i18nType {
|
||||
case "bot":
|
||||
localizer = LocalizerBot
|
||||
case "web":
|
||||
localizer = LocalizerWeb
|
||||
default:
|
||||
logger.Errorf("Invalid type for I18n: %s", i18nType)
|
||||
return ""
|
||||
}
|
||||
|
||||
templateData := createTemplateData(params)
|
||||
|
||||
msg, err := localizer.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: templateData,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to localize message: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func initTGBotLocalizer(settingService SettingService) error {
|
||||
botLang, err := settingService.GetTgLang()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
LocalizerBot = i18n.NewLocalizer(i18nBundle, botLang)
|
||||
return nil
|
||||
}
|
||||
|
||||
func LocalizerMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var lang string
|
||||
|
||||
if cookie, err := c.Request.Cookie("lang"); err == nil {
|
||||
lang = cookie.Value
|
||||
} else {
|
||||
lang = c.GetHeader("Accept-Language")
|
||||
}
|
||||
|
||||
LocalizerWeb = i18n.NewLocalizer(i18nBundle, lang)
|
||||
|
||||
c.Set("localizer", LocalizerWeb)
|
||||
c.Set("I18n", I18n)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
|
||||
err := fs.WalkDir(i18nFS, "translation",
|
||||
func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := i18nFS.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -39,6 +39,7 @@ var defaultValueMap = map[string]string{
|
||||
"tgRunTime": "@daily",
|
||||
"tgBotBackup": "false",
|
||||
"tgCpu": "0",
|
||||
"tgLang": "en-US",
|
||||
"subEnable": "false",
|
||||
"subListen": "",
|
||||
"subPort": "2096",
|
||||
@@ -248,6 +249,10 @@ func (s *SettingService) GetTgCpu() (int, error) {
|
||||
return s.getInt("tgCpu")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTgLang() (string, error) {
|
||||
return s.getString("tgLang")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetPort() (int, error) {
|
||||
return s.getInt("webPort")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/web/locale"
|
||||
"x-ui/xray"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
@@ -19,6 +21,7 @@ import (
|
||||
var bot *tgbotapi.BotAPI
|
||||
var adminIds []int64
|
||||
var isRunning bool
|
||||
var hostname string
|
||||
|
||||
type LoginStatus byte
|
||||
|
||||
@@ -38,7 +41,17 @@ func (t *Tgbot) NewTgbot() *Tgbot {
|
||||
return new(Tgbot)
|
||||
}
|
||||
|
||||
func (t *Tgbot) Start() error {
|
||||
func (t *Tgbot) I18nBot(name string, params ...string) string {
|
||||
return locale.I18n(locale.Bot, name, params...)
|
||||
}
|
||||
|
||||
func (t *Tgbot) Start(i18nFS embed.FS) error {
|
||||
err := locale.InitLocalizer(i18nFS, &t.settingService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.SetHostname()
|
||||
tgBottoken, err := t.settingService.GetTgBotToken()
|
||||
if err != nil || tgBottoken == "" {
|
||||
logger.Warning("Get TgBotToken failed:", err)
|
||||
@@ -77,10 +90,20 @@ func (t *Tgbot) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tgbot) IsRunnging() bool {
|
||||
func (t *Tgbot) IsRunning() bool {
|
||||
return isRunning
|
||||
}
|
||||
|
||||
func (t *Tgbot) SetHostname() {
|
||||
host, err := os.Hostname()
|
||||
if err != nil {
|
||||
logger.Error("get hostname error:", err)
|
||||
hostname = ""
|
||||
return
|
||||
}
|
||||
hostname = host
|
||||
}
|
||||
|
||||
func (t *Tgbot) Stop() {
|
||||
bot.StopReceivingUpdates()
|
||||
logger.Info("Stop Telegram receiver ...")
|
||||
@@ -115,16 +138,16 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
|
||||
// Extract the command from the Message.
|
||||
switch message.Command() {
|
||||
case "help":
|
||||
msg = "This bot is providing you some specefic data from the server.\n\n Please choose:"
|
||||
msg += t.I18nBot("tgbot.commands.help")
|
||||
msg += t.I18nBot("tgbot.commands.pleaseChoose")
|
||||
case "start":
|
||||
msg = "Hello <i>" + message.From.FirstName + "</i> 👋"
|
||||
msg += t.I18nBot("tgbot.commands.start", "Firstname=="+message.From.FirstName)
|
||||
if isAdmin {
|
||||
hostname, _ := os.Hostname()
|
||||
msg += "\nWelcome to <b>" + hostname + "</b> management bot"
|
||||
msg += t.I18nBot("tgbot.commands.welcome", "Hostname=="+hostname)
|
||||
}
|
||||
msg += "\n\nI can do some magics for you, please choose:"
|
||||
msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose")
|
||||
case "status":
|
||||
msg = "bot is ok ✅"
|
||||
msg += t.I18nBot("tgbot.commands.status")
|
||||
case "usage":
|
||||
if len(message.CommandArguments()) > 1 {
|
||||
if isAdmin {
|
||||
@@ -133,16 +156,16 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
|
||||
t.searchForClient(chatId, message.CommandArguments())
|
||||
}
|
||||
} else {
|
||||
msg = "❗Please provide a text for search!"
|
||||
msg += t.I18nBot("tgbot.commands.usage")
|
||||
}
|
||||
case "inbound":
|
||||
if isAdmin {
|
||||
t.searchInbound(chatId, message.CommandArguments())
|
||||
} else {
|
||||
msg = "❗ Unknown command"
|
||||
msg += t.I18nBot("tgbot.commands.unknown")
|
||||
}
|
||||
default:
|
||||
msg = "❗ Unknown command"
|
||||
msg += t.I18nBot("tgbot.commands.unknown")
|
||||
}
|
||||
t.SendAnswer(chatId, msg, isAdmin)
|
||||
}
|
||||
@@ -167,9 +190,9 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
|
||||
case "client_traffic":
|
||||
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
||||
case "client_commands":
|
||||
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Password]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
|
||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpClientCommands"))
|
||||
case "commands":
|
||||
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>")
|
||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,48 +206,52 @@ func checkAdmin(tgId int64) bool {
|
||||
}
|
||||
|
||||
func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
||||
var numericKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||
numericKeyboard := tgbotapi.NewInlineKeyboardMarkup(
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData("Server Usage", "get_usage"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("Get DB Backup", "get_backup"),
|
||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.serverUsage"), "get_usage"),
|
||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.dbBackup"), "get_backup"),
|
||||
),
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData("Get Inbounds", "inbounds"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("Deplete soon", "deplete_soon"),
|
||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.getInbounds"), "inbounds"),
|
||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.depleteSoon"), "deplete_soon"),
|
||||
),
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData("Commands", "commands"),
|
||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.commands"), "commands"),
|
||||
),
|
||||
)
|
||||
var numericKeyboardClient = tgbotapi.NewInlineKeyboardMarkup(
|
||||
numericKeyboardClient := tgbotapi.NewInlineKeyboardMarkup(
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData("Get Usage", "client_traffic"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("Commands", "client_commands"),
|
||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.clientUsage"), "client_traffic"),
|
||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.commands"), "client_commands"),
|
||||
),
|
||||
)
|
||||
msgConfig := tgbotapi.NewMessage(chatId, msg)
|
||||
msgConfig.ParseMode = "HTML"
|
||||
|
||||
var keyboardMarkup tgbotapi.InlineKeyboardMarkup
|
||||
if isAdmin {
|
||||
msgConfig.ReplyMarkup = numericKeyboard
|
||||
keyboardMarkup = numericKeyboard
|
||||
} else {
|
||||
msgConfig.ReplyMarkup = numericKeyboardClient
|
||||
}
|
||||
_, err := bot.Send(msgConfig)
|
||||
if err != nil {
|
||||
logger.Warning("Error sending telegram message :", err)
|
||||
keyboardMarkup = numericKeyboardClient
|
||||
}
|
||||
t.SendMsgToTgbot(chatId, msg, keyboardMarkup)
|
||||
}
|
||||
|
||||
func (t *Tgbot) SendMsgToTgbot(tgid int64, msg string) {
|
||||
func (t *Tgbot) SendMsgToTgbot(tgid int64, msg string, replyMarkup ...tgbotapi.InlineKeyboardMarkup) {
|
||||
if !isRunning {
|
||||
return
|
||||
}
|
||||
if msg == "" {
|
||||
logger.Info("[tgbot] message is empty!")
|
||||
return
|
||||
}
|
||||
|
||||
var allMessages []string
|
||||
limit := 2000
|
||||
|
||||
// paging message if it is big
|
||||
if len(msg) > limit {
|
||||
messages := strings.Split(msg, "\r\n \r\n")
|
||||
lastIndex := -1
|
||||
|
||||
for _, message := range messages {
|
||||
if (len(allMessages) == 0) || (len(allMessages[lastIndex])+len(message) > limit) {
|
||||
allMessages = append(allMessages, message)
|
||||
@@ -239,6 +266,9 @@ func (t *Tgbot) SendMsgToTgbot(tgid int64, msg string) {
|
||||
for _, message := range allMessages {
|
||||
info := tgbotapi.NewMessage(tgid, message)
|
||||
info.ParseMode = "HTML"
|
||||
if len(replyMarkup) > 0 {
|
||||
info.ReplyMarkup = replyMarkup[0]
|
||||
}
|
||||
_, err := bot.Send(info)
|
||||
if err != nil {
|
||||
logger.Warning("Error sending telegram message :", err)
|
||||
@@ -256,37 +286,44 @@ func (t *Tgbot) SendMsgToTgbotAdmins(msg string) {
|
||||
func (t *Tgbot) SendReport() {
|
||||
runTime, err := t.settingService.GetTgbotRuntime()
|
||||
if err == nil && len(runTime) > 0 {
|
||||
t.SendMsgToTgbotAdmins("🕰 Scheduled reports: " + runTime + "\r\nDate-Time: " + time.Now().Format("2006-01-02 15:04:05"))
|
||||
msg := ""
|
||||
msg += t.I18nBot("tgbot.messages.report", "RunTime=="+runTime)
|
||||
msg += t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||
t.SendMsgToTgbotAdmins(msg)
|
||||
}
|
||||
|
||||
info := t.getServerUsage()
|
||||
t.SendMsgToTgbotAdmins(info)
|
||||
|
||||
exhausted := t.getExhausted()
|
||||
t.SendMsgToTgbotAdmins(exhausted)
|
||||
|
||||
backupEnable, err := t.settingService.GetTgBotBackup()
|
||||
if err == nil && backupEnable {
|
||||
for _, adminId := range adminIds {
|
||||
t.sendBackup(int64(adminId))
|
||||
}
|
||||
t.SendBackupToAdmins()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tgbot) SendBackupToAdmins() {
|
||||
if !t.IsRunning() {
|
||||
return
|
||||
}
|
||||
for _, adminId := range adminIds {
|
||||
t.sendBackup(int64(adminId))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tgbot) getServerUsage() string {
|
||||
var info string
|
||||
//get hostname
|
||||
name, err := os.Hostname()
|
||||
if err != nil {
|
||||
logger.Error("get hostname error:", err)
|
||||
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
|
||||
info, ipv4, ipv6 := "", "", ""
|
||||
info += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||
info += t.I18nBot("tgbot.messages.version", "Version=="+config.GetVersion())
|
||||
|
||||
// get ip address
|
||||
netInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
logger.Error("net.Interfaces failed, err:", err.Error())
|
||||
info += "🌐 IP: Unknown\r\n \r\n"
|
||||
logger.Error("net.Interfaces failed, err: ", err.Error())
|
||||
info += t.I18nBot("tgbot.messages.ip", "IP=="+t.I18nBot("tgbot.unknown"))
|
||||
info += " \r\n"
|
||||
} else {
|
||||
for i := 0; i < len(netInterfaces); i++ {
|
||||
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
||||
@@ -295,7 +332,7 @@ func (t *Tgbot) getServerUsage() string {
|
||||
for _, address := range addrs {
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
ip += ipnet.IP.String() + " "
|
||||
ipv4 += ipnet.IP.String() + " "
|
||||
} else if ipnet.IP.To16() != nil && !ipnet.IP.IsLinkLocalUnicast() {
|
||||
ipv6 += ipnet.IP.String() + " "
|
||||
}
|
||||
@@ -303,42 +340,45 @@ func (t *Tgbot) getServerUsage() string {
|
||||
}
|
||||
}
|
||||
}
|
||||
info += fmt.Sprintf("🌐IP: %s\r\n🌐IPv6: %s\r\n", ip, ipv6)
|
||||
|
||||
info += t.I18nBot("tgbot.messages.ipv4", "IPv4=="+ipv4)
|
||||
info += t.I18nBot("tgbot.messages.ipv6", "IPv6=="+ipv6)
|
||||
}
|
||||
|
||||
// get latest status of server
|
||||
t.lastStatus = t.serverService.GetStatus(t.lastStatus)
|
||||
info += fmt.Sprintf("🔌Server Uptime: %d days\r\n", int(t.lastStatus.Uptime/86400))
|
||||
info += fmt.Sprintf("📈Server Load: %.1f, %.1f, %.1f\r\n", t.lastStatus.Loads[0], t.lastStatus.Loads[1], t.lastStatus.Loads[2])
|
||||
info += fmt.Sprintf("📋Server Memory: %s/%s\r\n", common.FormatTraffic(int64(t.lastStatus.Mem.Current)), common.FormatTraffic(int64(t.lastStatus.Mem.Total)))
|
||||
info += fmt.Sprintf("🔹TcpCount: %d\r\n", t.lastStatus.TcpCount)
|
||||
info += fmt.Sprintf("🔸UdpCount: %d\r\n", t.lastStatus.UdpCount)
|
||||
info += fmt.Sprintf("🚦Traffic: %s (↑%s,↓%s)\r\n", common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent+t.lastStatus.NetTraffic.Recv)), common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent)), common.FormatTraffic(int64(t.lastStatus.NetTraffic.Recv)))
|
||||
info += fmt.Sprintf("ℹXray status: %s", t.lastStatus.Xray.State)
|
||||
info += t.I18nBot("tgbot.messages.serverUpTime", "UpTime=="+strconv.FormatUint(t.lastStatus.Uptime/86400, 10), "Unit=="+t.I18nBot("tgbot.days"))
|
||||
info += t.I18nBot("tgbot.messages.serverLoad", "Load1=="+strconv.FormatFloat(t.lastStatus.Loads[0], 'f', 2, 64), "Load2=="+strconv.FormatFloat(t.lastStatus.Loads[1], 'f', 2, 64), "Load3=="+strconv.FormatFloat(t.lastStatus.Loads[2], 'f', 2, 64))
|
||||
info += t.I18nBot("tgbot.messages.serverMemory", "Current=="+common.FormatTraffic(int64(t.lastStatus.Mem.Current)), "Total=="+common.FormatTraffic(int64(t.lastStatus.Mem.Total)))
|
||||
info += t.I18nBot("tgbot.messages.tcpCount", "Count=="+strconv.Itoa(t.lastStatus.TcpCount))
|
||||
info += t.I18nBot("tgbot.messages.udpCount", "Count=="+strconv.Itoa(t.lastStatus.UdpCount))
|
||||
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent+t.lastStatus.NetTraffic.Recv)), "Upload=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent)), "Download=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Recv)))
|
||||
info += t.I18nBot("tgbot.messages.xrayStatus", "State=="+fmt.Sprint(t.lastStatus.Xray.State))
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status LoginStatus) {
|
||||
if !t.IsRunning() {
|
||||
return
|
||||
}
|
||||
|
||||
if username == "" || ip == "" || time == "" {
|
||||
logger.Warning("UserLoginNotify failed,invalid info")
|
||||
return
|
||||
}
|
||||
var msg string
|
||||
// Get hostname
|
||||
name, err := os.Hostname()
|
||||
if err != nil {
|
||||
logger.Warning("get hostname error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := ""
|
||||
if status == LoginSuccess {
|
||||
msg = fmt.Sprintf("✅ Successfully logged-in to the panel\r\nHostname:%s\r\n", name)
|
||||
msg += t.I18nBot("tgbot.messages.loginSuccess")
|
||||
} else if status == LoginFail {
|
||||
msg = fmt.Sprintf("❗ Login to the panel was unsuccessful\r\nHostname:%s\r\n", name)
|
||||
msg += t.I18nBot("tgbot.messages.loginFailed")
|
||||
}
|
||||
msg += fmt.Sprintf("⏰ Time:%s\r\n", time)
|
||||
msg += fmt.Sprintf("🆔 Username:%s\r\n", username)
|
||||
msg += fmt.Sprintf("🌐 IP:%s\r\n", ip)
|
||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||
msg += t.I18nBot("tgbot.messages.username", "Username=="+username)
|
||||
msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip)
|
||||
msg += t.I18nBot("tgbot.messages.time", "Time=="+time)
|
||||
|
||||
t.SendMsgToTgbotAdmins(msg)
|
||||
}
|
||||
|
||||
@@ -348,17 +388,19 @@ func (t *Tgbot) getInboundUsages() string {
|
||||
inbouds, err := t.inboundService.GetAllInbounds()
|
||||
if err != nil {
|
||||
logger.Warning("GetAllInbounds run failed:", err)
|
||||
info += "❌ Failed to get inbounds"
|
||||
info += t.I18nBot("tgbot.answers.getInboundsFailed")
|
||||
} else {
|
||||
// NOTE:If there no any sessions here,need to notify here
|
||||
// TODO:Sub-node push, automatic conversion format
|
||||
for _, inbound := range inbouds {
|
||||
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))
|
||||
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||
|
||||
if inbound.ExpiryTime == 0 {
|
||||
info += "Expire date: ♾ Unlimited\r\n \r\n"
|
||||
info += t.I18nBot("tgbot.messages.expire", "DateTime=="+t.I18nBot("tgbot.unlimited"))
|
||||
} else {
|
||||
info += fmt.Sprintf("Expire date:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||
info += t.I18nBot("tgbot.messages.expire", "DateTime=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,75 +409,92 @@ func (t *Tgbot) getInboundUsages() string {
|
||||
|
||||
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
||||
if len(tgUserName) == 0 {
|
||||
msg := "Your configuration is not found!\nYou should configure your telegram username and ask Admin to add it to your configuration."
|
||||
msg := t.I18nBot("tgbot.answers.askToAddUser")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
|
||||
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
msg := "❌ Something went wrong!"
|
||||
msg := t.I18nBot("tgbot.wentWrong")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
if len(traffics) == 0 {
|
||||
msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram username in your configuration(s).\n\nYour username: <b>@" + tgUserName + "</b>"
|
||||
msg := t.I18nBot("tgbot.answers.askToAddUserName", "TgUserName=="+tgUserName)
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
|
||||
for _, traffic := range traffics {
|
||||
expiryTime := ""
|
||||
if traffic.ExpiryTime == 0 {
|
||||
expiryTime = "♾Unlimited"
|
||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||
} else if traffic.ExpiryTime < 0 {
|
||||
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||
} else {
|
||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
total := ""
|
||||
if traffic.Total == 0 {
|
||||
total = "♾Unlimited"
|
||||
total = t.I18nBot("tgbot.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)
|
||||
|
||||
output := ""
|
||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
}
|
||||
t.SendAnswer(chatId, "Please choose:", false)
|
||||
t.SendAnswer(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), false)
|
||||
}
|
||||
|
||||
func (t *Tgbot) searchClient(chatId int64, email string) {
|
||||
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
msg := "❌ Something went wrong!"
|
||||
msg := t.I18nBot("tgbot.wentWrong")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
if traffic == nil {
|
||||
msg := "No result!"
|
||||
msg := t.I18nBot("tgbot.noResult")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
|
||||
expiryTime := ""
|
||||
if traffic.ExpiryTime == 0 {
|
||||
expiryTime = "♾Unlimited"
|
||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||
} else if traffic.ExpiryTime < 0 {
|
||||
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||
} else {
|
||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
total := ""
|
||||
if traffic.Total == 0 {
|
||||
total = "♾Unlimited"
|
||||
total = t.I18nBot("tgbot.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)
|
||||
|
||||
output := ""
|
||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
}
|
||||
|
||||
@@ -443,38 +502,55 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
||||
inbouds, err := t.inboundService.SearchInbounds(remark)
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
msg := "❌ Something went wrong!"
|
||||
msg := t.I18nBot("tgbot.wentWrong")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(inbouds) == 0 {
|
||||
msg := t.I18nBot("tgbot.noInbounds")
|
||||
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))
|
||||
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||
|
||||
if inbound.ExpiryTime == 0 {
|
||||
info += "Expire date: ♾ Unlimited\r\n \r\n"
|
||||
info += t.I18nBot("tgbot.messages.expire", "DateTime=="+t.I18nBot("tgbot.unlimited"))
|
||||
} else {
|
||||
info += fmt.Sprintf("Expire date:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||
info += t.I18nBot("tgbot.messages.expire", "DateTime=="+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"
|
||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||
} else if traffic.ExpiryTime < 0 {
|
||||
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||
} else {
|
||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
total := ""
|
||||
if traffic.Total == 0 {
|
||||
total = "♾Unlimited"
|
||||
total = t.I18nBot("tgbot.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)
|
||||
|
||||
output := ""
|
||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
}
|
||||
}
|
||||
@@ -484,32 +560,40 @@ func (t *Tgbot) searchForClient(chatId int64, query string) {
|
||||
traffic, err := t.inboundService.SearchClientTraffic(query)
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
msg := "❌ Something went wrong!"
|
||||
msg := t.I18nBot("tgbot.wentWrong")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
if traffic == nil {
|
||||
msg := "No result!"
|
||||
msg := t.I18nBot("tgbot.noResult")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
|
||||
expiryTime := ""
|
||||
if traffic.ExpiryTime == 0 {
|
||||
expiryTime = "♾Unlimited"
|
||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||
} else if traffic.ExpiryTime < 0 {
|
||||
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||
} else {
|
||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
total := ""
|
||||
if traffic.Total == 0 {
|
||||
total = "♾Unlimited"
|
||||
total = t.I18nBot("tgbot.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)
|
||||
|
||||
output := ""
|
||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
}
|
||||
|
||||
@@ -521,7 +605,7 @@ func (t *Tgbot) getExhausted() string {
|
||||
var exhaustedClients []xray.ClientTraffic
|
||||
var disabledInbounds []model.Inbound
|
||||
var disabledClients []xray.ClientTraffic
|
||||
output := ""
|
||||
|
||||
TrafficThreshold, err := t.settingService.GetTrafficDiff()
|
||||
if err == nil && TrafficThreshold > 0 {
|
||||
trDiff = int64(TrafficThreshold) * 1073741824
|
||||
@@ -534,6 +618,7 @@ func (t *Tgbot) getExhausted() string {
|
||||
if err != nil {
|
||||
logger.Warning("Unable to load Inbounds", err)
|
||||
}
|
||||
|
||||
for _, inbound := range inbounds {
|
||||
if inbound.Enable {
|
||||
if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) ||
|
||||
@@ -556,39 +641,63 @@ func (t *Tgbot) getExhausted() string {
|
||||
disabledInbounds = append(disabledInbounds, *inbound)
|
||||
}
|
||||
}
|
||||
output += fmt.Sprintf("Exhausted Inbounds count:\r\n🛑 Disabled: %d\r\n🔜 Deplete soon: %d\r\n \r\n", len(disabledInbounds), len(exhaustedInbounds))
|
||||
|
||||
// Inbounds
|
||||
output := ""
|
||||
output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.inbounds"))
|
||||
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledInbounds)))
|
||||
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedInbounds)))
|
||||
output += "\r\n \r\n"
|
||||
|
||||
if len(exhaustedInbounds) > 0 {
|
||||
output += "Exhausted Inbounds:\r\n"
|
||||
output += t.I18nBot("tgbot.messages.exhaustedMsg", "Type=="+t.I18nBot("tgbot.inbounds"))
|
||||
|
||||
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))
|
||||
output += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||
output += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||
output += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||
if inbound.ExpiryTime == 0 {
|
||||
output += "Expire date: ♾Unlimited\r\n \r\n"
|
||||
output += t.I18nBot("tgbot.messages.expire", "DateTime=="+t.I18nBot("tgbot.unlimited"))
|
||||
} else {
|
||||
output += fmt.Sprintf("Expire date:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||
output += t.I18nBot("tgbot.messages.expire", "DateTime=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
output += "\r\n \r\n"
|
||||
}
|
||||
}
|
||||
output += fmt.Sprintf("Exhausted Clients count:\r\n🛑 Exhausted: %d\r\n🔜 Deplete soon: %d\r\n \r\n", len(disabledClients), len(exhaustedClients))
|
||||
|
||||
// Clients
|
||||
output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
||||
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients)))
|
||||
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedClients)))
|
||||
output += "\r\n \r\n"
|
||||
|
||||
if len(exhaustedClients) > 0 {
|
||||
output += "Exhausted Clients:\r\n"
|
||||
output += t.I18nBot("tgbot.messages.exhaustedMsg", "Type=="+t.I18nBot("tgbot.clients"))
|
||||
|
||||
for _, traffic := range exhaustedClients {
|
||||
expiryTime := ""
|
||||
if traffic.ExpiryTime == 0 {
|
||||
expiryTime = "♾Unlimited"
|
||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||
} else if traffic.ExpiryTime < 0 {
|
||||
expiryTime += fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||
expiryTime += fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||
} else {
|
||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
total := ""
|
||||
if traffic.Total == 0 {
|
||||
total = "♾Unlimited"
|
||||
total = t.I18nBot("tgbot.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 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)
|
||||
|
||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||
output += "\r\n \r\n"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,14 +705,20 @@ func (t *Tgbot) getExhausted() string {
|
||||
}
|
||||
|
||||
func (t *Tgbot) sendBackup(chatId int64) {
|
||||
sendingTime := time.Now().Format("2006-01-02 15:04:05")
|
||||
t.SendMsgToTgbot(chatId, "Backup time: "+sendingTime)
|
||||
if !t.IsRunning() {
|
||||
return
|
||||
}
|
||||
|
||||
output := t.I18nBot("tgbot.messages.backupTime", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
|
||||
file := tgbotapi.FilePath(config.GetDBPath())
|
||||
msg := tgbotapi.NewDocument(chatId, file)
|
||||
_, err := bot.Send(msg)
|
||||
if err != nil {
|
||||
logger.Warning("Error in uploading backup: ", err)
|
||||
}
|
||||
|
||||
file = tgbotapi.FilePath(xray.GetConfigPath())
|
||||
msg = tgbotapi.NewDocument(chatId, file)
|
||||
_, err = bot.Send(msg)
|
||||
|
||||
@@ -332,6 +332,7 @@
|
||||
"manualBlockedDomains" = "List of Blocked Domains"
|
||||
"manualDirectIPs" = "List of Direct IPs"
|
||||
"manualDirectDomains" = "List of Direct Domains"
|
||||
"manualIPv4Domains" = "List of IPv4 Domains"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Modify Settings "
|
||||
@@ -339,3 +340,73 @@
|
||||
"modifyUser" = "Modify User "
|
||||
"originalUserPassIncorrect" = "Incorrect original username or password"
|
||||
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗ No result!"
|
||||
"wentWrong" = "❌ Something went wrong!"
|
||||
"noInbounds" = "❗ No inbound found!"
|
||||
"unlimited" = "♾ Unlimited"
|
||||
"day" = "Day"
|
||||
"days" = "Days"
|
||||
"unknown" = "Unknown"
|
||||
"inbounds" = "Inbounds"
|
||||
"clients" = "Clients"
|
||||
|
||||
[tgbot.commands]
|
||||
"unknown" = "❗ Unknown command"
|
||||
"pleaseChoose" = "👇 Please choose:\r\n"
|
||||
"help" = "🤖 Welcome to this bot! It's designed to offer you specific data from the server, and it allows you to make modifications as needed.\r\n\r\n"
|
||||
"start" = "👋 Hello <i>{{ .Firstname }}</i>.\r\n"
|
||||
"welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n"
|
||||
"status" = "✅ Bot is ok!"
|
||||
"usage" = "❗ Please provide a text to search!"
|
||||
"helpAdminCommands" = "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>"
|
||||
"helpClientCommands" = "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nUse UUID for vmess/vless and Password for Trojan."
|
||||
|
||||
[tgbot.messages]
|
||||
"cpuThreshold" = "🔴 The CPU usage {{ .Percent }}% is more than threshold {{ .Threshold }}%"
|
||||
"loginSuccess" = "✅ Successfully logged-in to the panel.\r\n"
|
||||
"loginFailed" = "❗️ Login to the panel failed.\r\n"
|
||||
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Date-Time: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Hostname: {{ .Hostname }}\r\n"
|
||||
"version" = "🚀 X-UI Version: {{ .Version }}\r\n"
|
||||
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||
"ip" = "🌐 IP: {{ .IP }}\r\n"
|
||||
"serverUpTime" = "⏳ Server Uptime: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||
"serverLoad" = "📈 Server Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||
"serverMemory" = "📋 Server Memory: {{ .Current }}/{{ .Total }}\r\n"
|
||||
"tcpCount" = "🔹 TcpCount: {{ .Count }}\r\n"
|
||||
"udpCount" = "🔸 UdpCount: {{ .Count }}\r\n"
|
||||
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Xray Status: {{ .State }}\r\n"
|
||||
"username" = "👤 Username: {{ .Username }}\r\n"
|
||||
"time" = "⏰ Time: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Port: {{ .Port }}\r\n"
|
||||
"expire" = "📅 Expire Date: {{ .DateTime }}\r\n \r\n"
|
||||
"expireIn" = "📅 Expire In: {{ .Time }}\r\n \r\n"
|
||||
"active" = "💡 Active: {{ .Enable }}\r\n"
|
||||
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||
"upload" = "🔼 Upload↑: {{ .Upload }}\r\n"
|
||||
"download" = "🔽 Download↓: {{ .Download }}\r\n"
|
||||
"total" = "🔄 Total: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
|
||||
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
|
||||
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
|
||||
"depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n"
|
||||
"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
|
||||
|
||||
[tgbot.buttons]
|
||||
"dbBackup" = "Get DB Backup"
|
||||
"serverUsage" = "Server Usage"
|
||||
"getInbounds" = "Get Inbounds"
|
||||
"depleteSoon" = "Deplete soon"
|
||||
"clientUsage" = "Get Usage"
|
||||
"commands" = "Commands"
|
||||
|
||||
[tgbot.answers]
|
||||
"getInboundsFailed" = "❌ Failed to get inbounds"
|
||||
"askToAddUser" = "Your configuration is not found!\r\nYou should configure your telegram username and ask your Admin to add it to your configuration."
|
||||
"askToAddUserName" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram username in your configuration(s).\r\n\r\nYour username: <b>@{{ .TgUserName }}</b>"
|
||||
|
||||
@@ -330,6 +330,7 @@
|
||||
"manualBlockedDomains" = "لیست دامنه های مسدود شده"
|
||||
"manualDirectIPs" = "لیست آیپی های مستقیم"
|
||||
"manualDirectDomains" = "لیست دامنه های مستقیم"
|
||||
"manualIPv4Domains" = "لیست دامنههای IPv4"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "ویرایش تنظیمات"
|
||||
@@ -337,3 +338,73 @@
|
||||
"modifyUser" = "ویرایش کاربر"
|
||||
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد "
|
||||
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد "
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗ نتیجهای یافت نشد!"
|
||||
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||
"unlimited" = "♾ نامحدود"
|
||||
"day" = "روز"
|
||||
"days" = "روزها"
|
||||
"unknown" = "نامشخص"
|
||||
"inbounds" = "ورودیها"
|
||||
"clients" = "کلاینتها"
|
||||
|
||||
[tgbot.commands]
|
||||
"unknown" = "❗ دستور ناشناخته"
|
||||
"pleaseChoose" = "👇 لطفاً انتخاب کنید:\r\n"
|
||||
"help" = "🤖 به این ربات خوش آمدید! این ربات برای ارائه دادههای خاص از سرور طراحی شده است و به شما امکان تغییرات لازم را میدهد.\r\n\r\n"
|
||||
"start" = "👋 سلام <i>{{ .Firstname }}</i>.\r\n"
|
||||
"welcome" = "🤖 به ربات مدیریت <b>{{ .Hostname }}</b> خوش آمدید.\r\n"
|
||||
"status" = "✅ ربات در حالت عادی است!"
|
||||
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
|
||||
"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n \r\nبرای جستجوی ورودیها (با آمار مشتری):\r\n<code>/inbound [توضیح]</code>"
|
||||
"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n<code>/usage [UUID|رمز عبور]</code>\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید."
|
||||
|
||||
[tgbot.messages]
|
||||
"cpuThreshold" = "🔴 میزان استفاده از CPU {{ .Percent }}% بیشتر از آستانه {{ .Threshold }}% است."
|
||||
"loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n"
|
||||
"loginFailed" = "❗️ ورود به پنل ناموفق بود.\r\n"
|
||||
"report" = "🕰 گزارشات زمانبندی شده: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ تاریخ-زمان: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 نام میزبان: {{ .Hostname }}\r\n"
|
||||
"version" = "🚀 نسخه X-UI: {{ .Version }}\r\n"
|
||||
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||
"ip" = "🌐 آدرس IP: {{ .IP }}\r\n"
|
||||
"serverUpTime" = "⏳ زمان کارکرد سرور: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||
"serverLoad" = "📈 بار سرور: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||
"serverMemory" = "📋 حافظه سرور: {{ .Current }}/{{ .Total }}\r\n"
|
||||
"tcpCount" = "🔹 تعداد ترافیک TCP: {{ .Count }}\r\n"
|
||||
"udpCount" = "🔸 تعداد ترافیک UDP: {{ .Count }}\r\n"
|
||||
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ وضعیت Xray: {{ .State }}\r\n"
|
||||
"username" = "👤 نام کاربری: {{ .Username }}\r\n"
|
||||
"time" = "⏰ زمان: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 ورودی: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 پورت: {{ .Port }}\r\n"
|
||||
"expire" = "📅 تاریخ انقضا: {{ .DateTime }}\r\n \r\n"
|
||||
"expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n \r\n"
|
||||
"active" = "💡 فعال: {{ .Enable }}\r\n"
|
||||
"email" = "📧 ایمیل: {{ .Email }}\r\n"
|
||||
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
|
||||
"download" = "🔽 دانلود↓: {{ .Download }}\r\n"
|
||||
"total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
|
||||
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
|
||||
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
|
||||
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
|
||||
"backupTime" = "🗄 زمان پشتیبانگیری: {{ .Time }}\r\n"
|
||||
|
||||
[tgbot.buttons]
|
||||
"dbBackup" = "دریافت پشتیبان پایگاه داده"
|
||||
"serverUsage" = "استفاده از سرور"
|
||||
"getInbounds" = "دریافت ورودیها"
|
||||
"depleteSoon" = "به زودی به پایان خواهد رسید"
|
||||
"clientUsage" = "دریافت آمار کاربر"
|
||||
"commands" = "دستورات"
|
||||
|
||||
[tgbot.answers]
|
||||
"getInboundsFailed" = "❌ دریافت ورودیها با خطا مواجه شد."
|
||||
"askToAddUser" = "پیکربندی شما یافت نشد!\r\nشما باید نام کاربری تلگرام خود را پیکربندی کنید و از مدیر خود درخواست اضافه کردن آن به پیکربندی خود بکنید."
|
||||
"askToAddUserName" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود درخواست استفاده از نام کاربری تلگرام خود در پیکربندی (ها) خود را بکنید.\r\n\r\nنام کاربری شما: <b>@{{ .TgUserName }}</b>"
|
||||
|
||||
@@ -331,6 +331,7 @@
|
||||
"manualBlockedDomains" = "Список заблокированных доменов"
|
||||
"manualDirectIPs" = "Список прямых IP-адресов"
|
||||
"manualDirectDomains" = "Список прямых доменов"
|
||||
"manualIPv4Domains" = "Список доменов IPv4"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Изменение настроек"
|
||||
@@ -338,3 +339,73 @@
|
||||
"modifyUser" = "Изменение пользователя "
|
||||
"originalUserPassIncorrect" = "Неверное имя пользователя или пароль"
|
||||
"userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены"
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗ Нет результатов!"
|
||||
"wentWrong" = "❌ Что-то пошло не так!"
|
||||
"noInbounds" = "❗ Входящих соединений не найдено!"
|
||||
"unlimited" = "♾ Неограниченно"
|
||||
"day" = "День"
|
||||
"days" = "Дней"
|
||||
"unknown" = "Неизвестно"
|
||||
"inbounds" = "Входящие"
|
||||
"clients" = "Клиенты"
|
||||
|
||||
[tgbot.commands]
|
||||
"unknown" = "❗ Неизвестная команда"
|
||||
"pleaseChoose" = "👇 Пожалуйста, выберите:\r\n"
|
||||
"help" = "🤖 Добро пожаловать в этого бота! Он предназначен для предоставления вам конкретных данных с сервера и позволяет вносить необходимые изменения.\r\n\r\n"
|
||||
"start" = "👋 Привет, <i>{{ .Firstname }}</i>.\r\n"
|
||||
"welcome" = "🤖 Добро пожаловать в бота управления <b>{{ .Hostname }}</b>.\r\n"
|
||||
"status" = "✅ Бот работает нормально!"
|
||||
"usage" = "❗ Пожалуйста, укажите текст для поиска!"
|
||||
"helpAdminCommands" = "Поиск по электронной почте клиента:\r\n<code>/usage [Email]</code>\r\n \r\nПоиск входящих соединений (со статистикой клиента):\r\n<code>/inbound [Remark]</code>"
|
||||
"helpClientCommands" = "Для получения статистики используйте следующую команду:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nИспользуйте UUID для vmess/vless и пароль для Trojan."
|
||||
|
||||
[tgbot.messages]
|
||||
"cpuThreshold" = "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%"
|
||||
"loginSuccess" = "✅ Успешный вход в панель.\r\n"
|
||||
"loginFailed" = "❗️ Ошибка входа в панель.\r\n"
|
||||
"report" = "🕰 Запланированные отчеты: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Дата и время: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Имя хоста: {{ .Hostname }}\r\n"
|
||||
"version" = "🚀 Версия X-UI: {{ .Version }}\r\n"
|
||||
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||
"ip" = "🌐 IP: {{ .IP }}\r\n"
|
||||
"serverUpTime" = "⏳ Время работы сервера: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||
"serverLoad" = "📈 Загрузка сервера: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||
"serverMemory" = "📋 Память сервера: {{ .Current }}/{{ .Total }}\r\n"
|
||||
"tcpCount" = "🔹 Количество TCP-соединений: {{ .Count }}\r\n"
|
||||
"udpCount" = "🔸 Количество UDP-соединений: {{ .Count }}\r\n"
|
||||
"traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Состояние Xray: {{ .State }}\r\n"
|
||||
"username" = "👤 Имя пользователя: {{ .Username }}\r\n"
|
||||
"time" = "⏰ Время: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Порт: {{ .Port }}\r\n"
|
||||
"expire" = "📅 Дата окончания: {{ .DateTime }}\r\n \r\n"
|
||||
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n \r\n"
|
||||
"active" = "💡 Активен: {{ .Enable }}\r\n"
|
||||
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||
"upload" = "🔼 Загрузка↑: {{ .Upload }}\r\n"
|
||||
"download" = "🔽 Скачивание↓: {{ .Download }}\r\n"
|
||||
"total" = "🔄 Всего: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||
"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n"
|
||||
"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n"
|
||||
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
||||
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n \r\n"
|
||||
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
||||
|
||||
[tgbot.buttons]
|
||||
"dbBackup" = "Получить резервную копию DB"
|
||||
"serverUsage" = "Использование сервера"
|
||||
"getInbounds" = "Получить входящие потоки"
|
||||
"depleteSoon" = "Скоро исчерпание"
|
||||
"clientUsage" = "Получить использование"
|
||||
"commands" = "Команды"
|
||||
|
||||
[tgbot.answers]
|
||||
"getInboundsFailed" = "❌ Не удалось получить входящие потоки."
|
||||
"askToAddUser" = "Конфигурация не найдена!\r\nВы должны настроить свое телеграм-имя пользователя и попросить вашего администратора добавить его в вашу конфигурацию."
|
||||
"askToAddUserName" = "Конфигурация не найдена!\r\nПожалуйста, попросите вашего администратора использовать ваше телеграм-имя пользователя в вашей конфигурации(ях).\r\n\r\nВаше имя пользователя: <b>@{{ .TgUserName }}</b>"
|
||||
|
||||
@@ -331,6 +331,7 @@
|
||||
"manualBlockedDomains" = "被阻止的域列表"
|
||||
"manualDirectIPs" = "直接 IP 列表"
|
||||
"manualDirectDomains" = "直接域列表"
|
||||
"manualIPv4Domains" = "IPv4 域名列表"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "修改设置"
|
||||
@@ -338,3 +339,73 @@
|
||||
"modifyUser" = "修改用户"
|
||||
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
||||
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗ 没有结果!"
|
||||
"wentWrong" = "❌ 出了点问题!"
|
||||
"noInbounds" = "❗ 没有找到入站连接!"
|
||||
"unlimited" = "♾ 无限制"
|
||||
"day" = "天"
|
||||
"days" = "天"
|
||||
"unknown" = "未知"
|
||||
"inbounds" = "入站连接"
|
||||
"clients" = "客户端"
|
||||
|
||||
[tgbot.commands]
|
||||
"unknown" = "❗ 未知命令"
|
||||
"pleaseChoose" = "👇 请选择:\r\n"
|
||||
"help" = "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n"
|
||||
"start" = "👋 你好,<i>{{ .Firstname }}</i>。\r\n"
|
||||
"welcome" = "🤖 欢迎来到<b>{{ .Hostname }}</b>管理机器人。\r\n"
|
||||
"status" = "✅ 机器人正常运行!"
|
||||
"usage" = "❗ 请输入要搜索的文本!"
|
||||
"helpAdminCommands" = "搜索客户端邮箱:\r\n<code>/usage [Email]</code>\r\n \r\n搜索入站连接(包含客户端统计信息):\r\n<code>/inbound [Remark]</code>"
|
||||
"helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\n对于vmess/vless,请使用UUID;对于Trojan,请使用密码。"
|
||||
|
||||
[tgbot.messages]
|
||||
"cpuThreshold" = "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%"
|
||||
"loginSuccess" = "✅ 成功登录到面板。\r\n"
|
||||
"loginFailed" = "❗️ 面板登录失败。\r\n"
|
||||
"report" = "🕰 定时报告:{{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 主机名:{{ .Hostname }}\r\n"
|
||||
"version" = "🚀 X-UI 版本:{{ .Version }}\r\n"
|
||||
"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n"
|
||||
"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n"
|
||||
"ip" = "🌐 IP:{{ .IP }}\r\n"
|
||||
"serverUpTime" = "⏳ 服务器运行时间:{{ .UpTime }} {{ .Unit }}\r\n"
|
||||
"serverLoad" = "📈 服务器负载:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||
"serverMemory" = "📋 服务器内存:{{ .Current }}/{{ .Total }}\r\n"
|
||||
"tcpCount" = "🔹 TCP 连接数:{{ .Count }}\r\n"
|
||||
"udpCount" = "🔸 UDP 连接数:{{ .Count }}\r\n"
|
||||
"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Xray 状态:{{ .State }}\r\n"
|
||||
"username" = "👤 用户名:{{ .Username }}\r\n"
|
||||
"time" = "⏰ 时间:{{ .Time }}\r\n"
|
||||
"inbound" = "📍 入站:{{ .Remark }}\r\n"
|
||||
"port" = "🔌 端口:{{ .Port }}\r\n"
|
||||
"expire" = "📅 过期日期:{{ .DateTime }}\r\n \r\n"
|
||||
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n \r\n"
|
||||
"active" = "💡 激活:{{ .Enable }}\r\n"
|
||||
"email" = "📧 邮箱:{{ .Email }}\r\n"
|
||||
"upload" = "🔼 上传↑:{{ .Upload }}\r\n"
|
||||
"download" = "🔽 下载↓:{{ .Download }}\r\n"
|
||||
"total" = "🔄 总计:{{ .UpDown }} / {{ .Total }}\r\n"
|
||||
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}:\r\n"
|
||||
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n"
|
||||
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
|
||||
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n \r\n"
|
||||
"backupTime" = "🗄 备份时间:{{ .Time }}\r\n"
|
||||
|
||||
[tgbot.buttons]
|
||||
"dbBackup" = "获取数据库备份"
|
||||
"serverUsage" = "服务器使用情况"
|
||||
"getInbounds" = "获取入站信息"
|
||||
"depleteSoon" = "即将耗尽"
|
||||
"clientUsage" = "获取使用情况"
|
||||
"commands" = "命令"
|
||||
|
||||
[tgbot.answers]
|
||||
"getInboundsFailed" = "❌ 获取入站信息失败。"
|
||||
"askToAddUser" = "找不到您的配置!\r\n您应该配置您的 Telegram 用户名,并要求管理员将其添加到您的配置中。"
|
||||
"askToAddUserName" = "找不到您的配置!\r\n请要求您的管理员在您的配置中使用您的 Telegram 用户名。\r\n\r\n您的用户名:<b>@{{ .TgUserName }}</b>"
|
||||
|
||||
110
web/web.go
110
web/web.go
@@ -18,16 +18,14 @@ import (
|
||||
"x-ui/util/common"
|
||||
"x-ui/web/controller"
|
||||
"x-ui/web/job"
|
||||
"x-ui/web/locale"
|
||||
"x-ui/web/network"
|
||||
"x-ui/web/service"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/robfig/cron/v3"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
//go:embed assets/*
|
||||
@@ -179,13 +177,23 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
c.Header("Cache-Control", "max-age=31536000")
|
||||
}
|
||||
})
|
||||
err = s.initI18n(engine)
|
||||
|
||||
// init i18n
|
||||
err = locale.InitLocalizer(i18nFS, &s.settingService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply locale middleware for i18n
|
||||
i18nWebFunc := func(key string, params ...string) string {
|
||||
return locale.I18n(locale.Web, key, params...)
|
||||
}
|
||||
engine.FuncMap["i18n"] = i18nWebFunc
|
||||
engine.Use(locale.LocalizerMiddleware())
|
||||
|
||||
// set static files and template
|
||||
if config.IsDebug() {
|
||||
// for develop
|
||||
// for development
|
||||
files, err := s.getHtmlFiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -193,12 +201,12 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
engine.LoadHTMLFiles(files...)
|
||||
engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets")))
|
||||
} else {
|
||||
// for prod
|
||||
t, err := s.getHtmlTemplate(engine.FuncMap)
|
||||
// for production
|
||||
template, err := s.getHtmlTemplate(engine.FuncMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
engine.SetHTMLTemplate(t)
|
||||
engine.SetHTMLTemplate(template)
|
||||
engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS}))
|
||||
}
|
||||
|
||||
@@ -212,85 +220,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
func (s *Server) initI18n(engine *gin.Engine) error {
|
||||
bundle := i18n.NewBundle(language.SimplifiedChinese)
|
||||
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||
err := fs.WalkDir(i18nFS, "translation", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
data, err := i18nFS.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = bundle.ParseMessageFileBytes(data, path)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
findI18nParamNames := func(key string) []string {
|
||||
names := make([]string, 0)
|
||||
keyLen := len(key)
|
||||
for i := 0; i < keyLen-1; i++ {
|
||||
if key[i:i+2] == "{{" {
|
||||
j := i + 2
|
||||
isFind := false
|
||||
for ; j < keyLen-1; j++ {
|
||||
if key[j:j+2] == "}}" {
|
||||
isFind = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isFind {
|
||||
names = append(names, key[i+3:j])
|
||||
}
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
var localizer *i18n.Localizer
|
||||
|
||||
I18n := func(key string, params ...string) (string, error) {
|
||||
names := findI18nParamNames(key)
|
||||
if len(names) != len(params) {
|
||||
return "", common.NewError("find names:", names, "---------- params:", params, "---------- num not equal")
|
||||
}
|
||||
templateData := map[string]interface{}{}
|
||||
for i := range names {
|
||||
templateData[names[i]] = params[i]
|
||||
}
|
||||
return localizer.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: templateData,
|
||||
})
|
||||
}
|
||||
|
||||
engine.FuncMap["i18n"] = I18n
|
||||
|
||||
engine.Use(func(c *gin.Context) {
|
||||
var lang string
|
||||
|
||||
if cookie, err := c.Request.Cookie("lang"); err == nil {
|
||||
lang = cookie.Value
|
||||
} else {
|
||||
lang = c.GetHeader("Accept-Language")
|
||||
}
|
||||
|
||||
localizer = i18n.NewLocalizer(bundle, lang)
|
||||
c.Set("localizer", localizer)
|
||||
c.Set("I18n", I18n)
|
||||
c.Next()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) startTask() {
|
||||
err := s.xrayService.RestartXray(true)
|
||||
if err != nil {
|
||||
@@ -314,7 +243,7 @@ func (s *Server) startTask() {
|
||||
if (err == nil) && (isTgbotenabled) {
|
||||
runtime, err := s.settingService.GetTgbotRuntime()
|
||||
if err != nil || runtime == "" {
|
||||
logger.Errorf("Add NewStatsNotifyJob error[%s],Runtime[%s] invalid,wil run default", err, runtime)
|
||||
logger.Errorf("Add NewStatsNotifyJob error[%s], Runtime[%s] invalid, will run default", err, runtime)
|
||||
runtime = "@daily"
|
||||
}
|
||||
logger.Infof("Tg notify enabled,run at %s", runtime)
|
||||
@@ -329,7 +258,6 @@ func (s *Server) startTask() {
|
||||
if (err == nil) && (cpuThreshold > 0) {
|
||||
s.cron.AddJob("@every 10s", job.NewCheckCpuJob())
|
||||
}
|
||||
|
||||
} else {
|
||||
s.cron.Remove(entry)
|
||||
}
|
||||
@@ -409,7 +337,7 @@ func (s *Server) Start() (err error) {
|
||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||
if (err == nil) && (isTgbotenabled) {
|
||||
tgBot := s.tgbotService.NewTgbot()
|
||||
tgBot.Start()
|
||||
tgBot.Start(i18nFS)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -421,7 +349,7 @@ func (s *Server) Stop() error {
|
||||
if s.cron != nil {
|
||||
s.cron.Stop()
|
||||
}
|
||||
if s.tgbotService.IsRunnging() {
|
||||
if s.tgbotService.IsRunning() {
|
||||
s.tgbotService.Stop()
|
||||
}
|
||||
var err1 error
|
||||
|
||||
Reference in New Issue
Block a user