mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-07-02 04:52:08 +00:00
Merge pull request #491 from hamid-gh98/main
[tgbot] Multi language + More...
This commit is contained in:
commit
5f489c3d08
27 changed files with 1316 additions and 494 deletions
16
.gitignore
vendored
16
.gitignore
vendored
|
@ -1,15 +1,15 @@
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.cache
|
||||||
|
.sync*
|
||||||
|
*.tar.gz
|
||||||
|
access.log
|
||||||
|
error.log
|
||||||
tmp
|
tmp
|
||||||
|
main
|
||||||
backup/
|
backup/
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
x-ui-*.tar.gz
|
|
||||||
/x-ui
|
|
||||||
/release.sh
|
|
||||||
.sync*
|
|
||||||
main
|
|
||||||
release/
|
release/
|
||||||
access.log
|
/release.sh
|
||||||
error.log
|
/x-ui
|
||||||
.cache
|
|
||||||
|
|
|
@ -194,6 +194,7 @@ Reference syntax:
|
||||||
| `GET` | `"/list"` | Get all inbounds |
|
| `GET` | `"/list"` | Get all inbounds |
|
||||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||||
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
|
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
|
||||||
|
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
||||||
| `POST` | `"/add"` | Add inbound |
|
| `POST` | `"/add"` | Add inbound |
|
||||||
| `POST` | `"/del/:id"` | Delete Inbound |
|
| `POST` | `"/del/:id"` | Delete Inbound |
|
||||||
| `POST` | `"/update/:id"` | Update Inbound |
|
| `POST` | `"/update/:id"` | Update Inbound |
|
||||||
|
|
|
@ -181,6 +181,7 @@ class AllSetting {
|
||||||
this.tgRunTime = "@daily";
|
this.tgRunTime = "@daily";
|
||||||
this.tgBotBackup = false;
|
this.tgBotBackup = false;
|
||||||
this.tgCpu = "";
|
this.tgCpu = "";
|
||||||
|
this.tgLang = "";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
this.secretEnable = false;
|
this.secretEnable = false;
|
||||||
|
|
||||||
|
|
|
@ -102,5 +102,5 @@ func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) createBackup(c *gin.Context) {
|
func (a *APIController) createBackup(c *gin.Context) {
|
||||||
a.Tgbot.SendBackUP(c)
|
a.Tgbot.SendBackupToAdmins()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/web/locale"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -13,7 +15,7 @@ type BaseController struct {
|
||||||
func (a *BaseController) checkLogin(c *gin.Context) {
|
func (a *BaseController) checkLogin(c *gin.Context) {
|
||||||
if !session.IsLogin(c) {
|
if !session.IsLogin(c) {
|
||||||
if isAjax(c) {
|
if isAjax(c) {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.loginAgain"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain"))
|
||||||
} else {
|
} else {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||||
}
|
}
|
||||||
|
@ -23,11 +25,13 @@ func (a *BaseController) checkLogin(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func I18n(c *gin.Context, name string) string {
|
func I18nWeb(c *gin.Context, name string, params ...string) string {
|
||||||
anyfunc, _ := c.Get("I18n")
|
anyfunc, funcExists := c.Get("I18n")
|
||||||
i18n, _ := anyfunc.(func(key string, params ...string) (string, error))
|
if !funcExists {
|
||||||
|
logger.Warning("I18n function not exists in gin context!")
|
||||||
message, _ := i18n(name)
|
return ""
|
||||||
|
}
|
||||||
return message
|
i18nFunc, _ := anyfunc.(func(i18nType locale.I18nType, key string, keyParams ...string) string)
|
||||||
|
msg := i18nFunc(locale.Web, name, params...)
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, inbounds, nil)
|
jsonObj(c, inbounds, nil)
|
||||||
|
@ -68,12 +68,12 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
||||||
func (a *InboundController) getInbound(c *gin.Context) {
|
func (a *InboundController) getInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "get"), err)
|
jsonMsg(c, I18nWeb(c, "get"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err := a.inboundService.GetInbound(id)
|
inbound, err := a.inboundService.GetInbound(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, inbound, nil)
|
jsonObj(c, inbound, nil)
|
||||||
|
@ -93,7 +93,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.create"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.create"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
|
@ -101,7 +101,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
inbound.Enable = true
|
inbound.Enable = true
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
inbound, err = a.inboundService.AddInbound(inbound)
|
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 {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
@ -110,11 +110,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
func (a *InboundController) delInbound(c *gin.Context) {
|
func (a *InboundController) delInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "delete"), err)
|
jsonMsg(c, I18nWeb(c, "delete"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.inboundService.DelInbound(id)
|
err = a.inboundService.DelInbound(id)
|
||||||
jsonMsgObj(c, I18n(c, "delete"), id, err)
|
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
||||||
func (a *InboundController) updateInbound(c *gin.Context) {
|
func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound := &model.Inbound{
|
inbound := &model.Inbound{
|
||||||
|
@ -131,11 +131,11 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
}
|
}
|
||||||
err = c.ShouldBind(inbound)
|
err = c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err = a.inboundService.UpdateInbound(inbound)
|
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 {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||||
data := &model.Inbound{}
|
data := &model.Inbound{}
|
||||||
err := c.ShouldBind(data)
|
err := c.ShouldBind(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientId := c.Param("clientId")
|
clientId := c.Param("clientId")
|
||||||
|
@ -205,7 +205,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +223,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||||
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
@ -251,7 +251,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||||
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +266,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||||
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.inboundService.DelDepletedClients(id)
|
err = a.inboundService.DelDepletedClients(id)
|
||||||
|
|
|
@ -49,26 +49,27 @@ func (a *IndexController) login(c *gin.Context) {
|
||||||
var form LoginForm
|
var form LoginForm
|
||||||
err := c.ShouldBind(&form)
|
err := c.ShouldBind(&form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.invalidFormData"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Username == "" {
|
if form.Username == "" {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyUsername"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Password == "" {
|
if form.Password == "" {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
||||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||||
if user == nil {
|
if user == nil {
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
|
||||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
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
|
return
|
||||||
} else {
|
} 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)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ func (a *IndexController) login(c *gin.Context) {
|
||||||
|
|
||||||
err = session.SetLoginUser(c, user)
|
err = session.SetLoginUser(c, user)
|
||||||
logger.Info("user", user.Id, "login success")
|
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) {
|
func (a *IndexController) logout(c *gin.Context) {
|
||||||
|
|
|
@ -81,7 +81,7 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
|
||||||
|
|
||||||
versions, err := a.serverService.GetXrayVersions()
|
versions, err := a.serverService.GetXrayVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getVersion"), err)
|
jsonMsg(c, I18nWeb(c, "getVersion"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
|
||||||
func (a *ServerController) installXray(c *gin.Context) {
|
func (a *ServerController) installXray(c *gin.Context) {
|
||||||
version := c.Param("version")
|
version := c.Param("version")
|
||||||
err := a.serverService.UpdateXray(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) {
|
func (a *ServerController) stopXrayService(c *gin.Context) {
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
allSetting, err := a.settingService.GetAllSetting()
|
allSetting, err := a.settingService.GetAllSetting()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, allSetting, nil)
|
jsonObj(c, allSetting, nil)
|
||||||
|
@ -58,7 +58,7 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
||||||
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, defaultJsonConfig, nil)
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
|
@ -67,22 +67,22 @@ func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
expireDiff, err := a.settingService.GetExpireDiff()
|
expireDiff, err := a.settingService.GetExpireDiff()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
trafficDiff, err := a.settingService.GetTrafficDiff()
|
trafficDiff, err := a.settingService.GetTrafficDiff()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defaultCert, err := a.settingService.GetCertFile()
|
defaultCert, err := a.settingService.GetCertFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defaultKey, err := a.settingService.GetKeyFile()
|
defaultKey, err := a.settingService.GetKeyFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
|
@ -98,27 +98,27 @@ func (a *SettingController) updateSetting(c *gin.Context) {
|
||||||
allSetting := &entity.AllSetting{}
|
allSetting := &entity.AllSetting{}
|
||||||
err := c.ShouldBind(allSetting)
|
err := c.ShouldBind(allSetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.settingService.UpdateAllSetting(allSetting)
|
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) {
|
func (a *SettingController) updateUser(c *gin.Context) {
|
||||||
form := &updateUserForm{}
|
form := &updateUserForm{}
|
||||||
err := c.ShouldBind(form)
|
err := c.ShouldBind(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
if user.Username != form.OldUsername || user.Password != form.OldPassword {
|
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
|
return
|
||||||
}
|
}
|
||||||
if form.NewUsername == "" || form.NewPassword == "" {
|
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
|
return
|
||||||
}
|
}
|
||||||
err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
|
err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
|
||||||
|
@ -127,19 +127,19 @@ func (a *SettingController) updateUser(c *gin.Context) {
|
||||||
user.Password = form.NewPassword
|
user.Password = form.NewPassword
|
||||||
session.SetLoginUser(c, user)
|
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) {
|
func (a *SettingController) restartPanel(c *gin.Context) {
|
||||||
err := a.panelService.RestartPanel(time.Second * 3)
|
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) updateSecret(c *gin.Context) {
|
func (a *SettingController) updateSecret(c *gin.Context) {
|
||||||
form := &updateSecretForm{}
|
form := &updateSecretForm{}
|
||||||
err := c.ShouldBind(form)
|
err := c.ShouldBind(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
||||||
|
@ -147,7 +147,7 @@ func (a *SettingController) updateSecret(c *gin.Context) {
|
||||||
user.LoginSecret = form.LoginSecret
|
user.LoginSecret = form.LoginSecret
|
||||||
session.SetLoginUser(c, user)
|
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) getUserSecret(c *gin.Context) {
|
func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||||
|
|
|
@ -38,12 +38,12 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
m.Success = true
|
m.Success = true
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
m.Msg = msg + I18n(c, "success")
|
m.Msg = msg + I18nWeb(c, "success")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.Success = false
|
m.Success = false
|
||||||
m.Msg = msg + I18n(c, "fail") + ": " + err.Error()
|
m.Msg = msg + I18nWeb(c, "fail") + ": " + err.Error()
|
||||||
logger.Warning(msg+I18n(c, "fail")+": ", err)
|
logger.Warning(msg+I18nWeb(c, "fail")+": ", err)
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusOK, m)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ type AllSetting struct {
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||||
|
|
82
web/global/hashStorage.go
Normal file
82
web/global/hashStorage.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HashEntry struct {
|
||||||
|
Hash string
|
||||||
|
Value string
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type HashStorage struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Data map[string]HashEntry
|
||||||
|
Expiration time.Duration
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHashStorage(expiration time.Duration) *HashStorage {
|
||||||
|
return &HashStorage{
|
||||||
|
Data: make(map[string]HashEntry),
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) SaveHash(query string) string {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
md5Hash := md5.Sum([]byte(query))
|
||||||
|
md5HashString := hex.EncodeToString(md5Hash[:])
|
||||||
|
|
||||||
|
entry := HashEntry{
|
||||||
|
Hash: md5HashString,
|
||||||
|
Value: query,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Data[md5HashString] = entry
|
||||||
|
|
||||||
|
return md5HashString
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (h *HashStorage) GetValue(hash string) (string, bool) {
|
||||||
|
h.RLock()
|
||||||
|
defer h.RUnlock()
|
||||||
|
|
||||||
|
entry, exists := h.Data[hash]
|
||||||
|
|
||||||
|
return entry.Value, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) IsMD5(hash string) bool {
|
||||||
|
match, _ := regexp.MatchString("^[a-f0-9]{32}$", hash)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) RemoveExpiredHashes() {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for hash, entry := range h.Data {
|
||||||
|
if now.Sub(entry.Timestamp) > h.Expiration {
|
||||||
|
delete(h.Data, hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) Reset() {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
h.Data = make(map[string]HashEntry)
|
||||||
|
}
|
|
@ -100,7 +100,7 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- xtls settings -->
|
<!-- xtls settings -->
|
||||||
<a-form v-if="inbound.xtls" layout="inline">
|
<a-form v-else-if="inbound.xtls" layout="inline">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -105,6 +105,10 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
|
<a-switch v-model="enableFilter"
|
||||||
|
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-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">
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
||||||
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
|
@ -112,10 +116,6 @@
|
||||||
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||||
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<a-switch v-model="enableFilter"
|
|
||||||
checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
|
|
||||||
@change="toggleFilter">
|
|
||||||
</a-switch>
|
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1300 }"
|
:loading="spinning" :scroll="{ x: 1300 }"
|
||||||
|
|
|
@ -23,6 +23,34 @@
|
||||||
:not(.ant-card-dark)>.ant-tabs-top-bar {
|
:not(.ant-card-dark)>.ant-tabs-top-bar {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-msg {
|
||||||
|
color: rgb(194, 117, 18);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 16px 6px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
|
@ -35,8 +63,14 @@
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<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-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-tabs style="margin:1rem 0.5rem;" default-active-key="1" :class="themeSwitcher.darkCardClass" >
|
<a-tabs style="margin:1rem 0.5rem;" default-active-key="1" :class="themeSwitcher.darkCardClass">
|
||||||
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings"}}'>
|
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="alert-msg">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.infoDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
|
||||||
|
@ -72,12 +106,6 @@
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 16px; padding: 5px 5px; text-align: center;">
|
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
|
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
|
||||||
<a-tabs class="ant-card-dark-securitybox-nohover" default-active-key="sec-1" :class="themeSwitcher.darkCardClass">
|
<a-tabs class="ant-card-dark-securitybox-nohover" default-active-key="sec-1" :class="themeSwitcher.darkCardClass">
|
||||||
|
@ -144,8 +172,8 @@
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-divider style="padding: 20px;">{{ i18n "pages.settings.templates.title"}} </a-divider>
|
<a-divider style="padding: 20px;">{{ i18n "pages.settings.templates.title"}} </a-divider>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 16px; padding: 5px 5px; text-align: center;">
|
<h2 class="alert-msg">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-icon type="warning"></a-icon>
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
{{ i18n "pages.settings.infoDesc" }}
|
||||||
</h2>
|
</h2>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -154,8 +182,8 @@
|
||||||
<a-collapse>
|
<a-collapse>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.generalConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.generalConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<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;">
|
<h2 class="collapse-title">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-icon type="warning"></a-icon>
|
||||||
{{ i18n "pages.settings.templates.generalConfigsDesc" }}
|
{{ i18n "pages.settings.templates.generalConfigsDesc" }}
|
||||||
</h2>
|
</h2>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -199,8 +227,8 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<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;">
|
<h2 class="collapse-title">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-icon type="warning"></a-icon>
|
||||||
{{ i18n "pages.settings.templates.blockConfigsDesc" }}
|
{{ i18n "pages.settings.templates.blockConfigsDesc" }}
|
||||||
</h2>
|
</h2>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -212,8 +240,8 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockCountryConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockCountryConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<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;">
|
<h2 class="collapse-title">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-icon type="warning"></a-icon>
|
||||||
{{ i18n "pages.settings.templates.blockCountryConfigsDesc" }}
|
{{ i18n "pages.settings.templates.blockCountryConfigsDesc" }}
|
||||||
</h2>
|
</h2>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -226,8 +254,8 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.directCountryConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.directCountryConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<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;">
|
<h2 class="collapse-title">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-icon type="warning"></a-icon>
|
||||||
{{ i18n "pages.settings.templates.directCountryConfigsDesc" }}
|
{{ i18n "pages.settings.templates.directCountryConfigsDesc" }}
|
||||||
</h2>
|
</h2>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -240,8 +268,8 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.ipv4Configs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.ipv4Configs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<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;">
|
<h2 class="collapse-title">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-icon type="warning"></a-icon>
|
||||||
{{ i18n "pages.settings.templates.ipv4ConfigsDesc" }}
|
{{ i18n "pages.settings.templates.ipv4ConfigsDesc" }}
|
||||||
</h2>
|
</h2>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -250,8 +278,8 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.warpConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.warpConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<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;">
|
<h2 class="collapse-title">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-icon type="warning"></a-icon>
|
||||||
{{ i18n "pages.settings.templates.warpConfigsDesc" }}
|
{{ i18n "pages.settings.templates.warpConfigsDesc" }}
|
||||||
</h2>
|
</h2>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -262,8 +290,8 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualLists"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualLists"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<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;">
|
<h2 class="collapse-title">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-icon type="warning"></a-icon>
|
||||||
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
||||||
</h2>
|
</h2>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -271,6 +299,8 @@
|
||||||
<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.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.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.manualDirectDomains"}}' v-model="manualDirectDomains"></setting-list-item>
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualIPv4Domains"}}' v-model="manualIPv4Domains"></setting-list-item>
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualWARPDomains"}}' v-model="manualWARPDomains"></setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
@ -295,6 +325,12 @@
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
<a-tab-pane key="4" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="alert-msg">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.infoDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
||||||
|
@ -302,13 +338,30 @@
|
||||||
<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="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="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>
|
<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>
|
<a-list-item>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row style="padding: 20px">
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 16px; padding: 5px 5px; text-align: center;">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<a-list-item-meta title="Telegram Bot Language" />
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
</a-col>
|
||||||
</h2>
|
|
||||||
|
<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-row>
|
||||||
|
</a-list-item>
|
||||||
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
@ -452,7 +505,12 @@
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
await PromiseUtil.sleep(5000);
|
await PromiseUtil.sleep(5000);
|
||||||
window.location.replace(this.allSetting.webBasePath + "panel/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 fetchUserSecret() {
|
async fetchUserSecret() {
|
||||||
|
@ -584,30 +642,30 @@
|
||||||
computed: {
|
computed: {
|
||||||
templateSettings: {
|
templateSettings: {
|
||||||
get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; },
|
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: {
|
inboundSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
newTemplateSettings.inbounds = JSON.parse(newValue)
|
newTemplateSettings.inbounds = JSON.parse(newValue);
|
||||||
this.templateSettings = newTemplateSettings
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
outboundSettings: {
|
outboundSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
newTemplateSettings.outbounds = JSON.parse(newValue)
|
newTemplateSettings.outbounds = JSON.parse(newValue);
|
||||||
this.templateSettings = newTemplateSettings
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
routingRuleSettings: {
|
routingRuleSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
newTemplateSettings.routing.rules = JSON.parse(newValue)
|
newTemplateSettings.routing.rules = JSON.parse(newValue);
|
||||||
this.templateSettings = newTemplateSettings
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
freedomStrategy: {
|
freedomStrategy: {
|
||||||
|
@ -682,6 +740,24 @@
|
||||||
this.syncRulesWithOutbound("direct", this.directSettings);
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
warpDomains: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "WARP", property: "domain", data: newValue });
|
||||||
|
this.syncRulesWithOutbound("WARP", this.warpSettings);
|
||||||
|
}
|
||||||
|
},
|
||||||
manualBlockedIPs: {
|
manualBlockedIPs: {
|
||||||
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
|
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
|
||||||
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
|
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
|
||||||
|
@ -698,6 +774,14 @@
|
||||||
get: function () { return JSON.stringify(this.directDomains, null, 2); },
|
get: function () { return JSON.stringify(this.directDomains, null, 2); },
|
||||||
set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
|
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)
|
||||||
|
},
|
||||||
|
manualWARPDomains: {
|
||||||
|
get: function () { return JSON.stringify(this.warpDomains, null, 2); },
|
||||||
|
set: debounce(function (value) { this.warpDomains = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
torrentSettings: {
|
torrentSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
|
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
|
||||||
|
@ -763,40 +847,26 @@
|
||||||
},
|
},
|
||||||
GoogleIPv4Settings: {
|
GoogleIPv4Settings: {
|
||||||
get: function () {
|
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) {
|
set: function (newValue) {
|
||||||
oldData = this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
oldData = [...oldData, ...this.settingsData.domains.google];
|
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
|
||||||
} else {
|
} 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: {
|
NetflixIPv4Settings: {
|
||||||
get: function () {
|
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) {
|
set: function (newValue) {
|
||||||
oldData = this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
oldData = [...oldData, ...this.settingsData.domains.netflix];
|
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
|
||||||
} else {
|
} 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: {
|
IRIpSettings: {
|
||||||
|
@ -945,78 +1015,50 @@
|
||||||
},
|
},
|
||||||
GoogleWARPSettings: {
|
GoogleWARPSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return doAllItemsExist(this.settingsData.domains.google, this.templateRuleGetter({ outboundTag: "WARP", property: "domain" }));
|
return doAllItemsExist(this.settingsData.domains.google, this.warpDomains);
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
oldData = this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
oldData = [...oldData, ...this.settingsData.domains.google];
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.google];
|
||||||
} else {
|
} else {
|
||||||
oldData = oldData.filter(data => !this.settingsData.domains.google.includes(data))
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.google.includes(data));
|
||||||
}
|
}
|
||||||
this.templateRuleSetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: oldData
|
|
||||||
});
|
|
||||||
this.syncRulesWithOutbound("WARP", this.warpSettings);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OpenAIWARPSettings: {
|
OpenAIWARPSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return doAllItemsExist(this.settingsData.domains.openai, this.templateRuleGetter({ outboundTag: "WARP", property: "domain" }));
|
return doAllItemsExist(this.settingsData.domains.openai, this.warpDomains);
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
oldData = this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
oldData = [...oldData, ...this.settingsData.domains.openai];
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.openai];
|
||||||
} else {
|
} else {
|
||||||
oldData = oldData.filter(data => !this.settingsData.domains.openai.includes(data))
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.openai.includes(data));
|
||||||
}
|
}
|
||||||
this.templateRuleSetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: oldData
|
|
||||||
});
|
|
||||||
this.syncRulesWithOutbound("WARP", this.warpSettings);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NetflixWARPSettings: {
|
NetflixWARPSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return doAllItemsExist(this.settingsData.domains.netflix, this.templateRuleGetter({ outboundTag: "WARP", property: "domain" }));
|
return doAllItemsExist(this.settingsData.domains.netflix, this.warpDomains);
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
oldData = this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
oldData = [...oldData, ...this.settingsData.domains.netflix];
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.netflix];
|
||||||
} else {
|
} else {
|
||||||
oldData = oldData.filter(data => !this.settingsData.domains.netflix.includes(data))
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
||||||
}
|
}
|
||||||
this.templateRuleSetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: oldData
|
|
||||||
});
|
|
||||||
this.syncRulesWithOutbound("WARP", this.warpSettings);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SpotifyWARPSettings: {
|
SpotifyWARPSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return doAllItemsExist(this.settingsData.domains.spotify, this.templateRuleGetter({ outboundTag: "WARP", property: "domain" }));
|
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
oldData = this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
oldData = [...oldData, ...this.settingsData.domains.spotify];
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.spotify];
|
||||||
} else {
|
} else {
|
||||||
oldData = oldData.filter(data => !this.settingsData.domains.spotify.includes(data))
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.spotify.includes(data));
|
||||||
}
|
}
|
||||||
this.templateRuleSetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: oldData
|
|
||||||
});
|
|
||||||
this.syncRulesWithOutbound("WARP", this.warpSettings);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
@ -24,7 +24,10 @@ func (j *CheckCpuJob) Run() {
|
||||||
// get latest status of server
|
// get latest status of server
|
||||||
percent, err := cpu.Percent(1*time.Second, false)
|
percent, err := cpu.Percent(1*time.Second, false)
|
||||||
if err == nil && percent[0] > float64(threshold) {
|
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)
|
j.tgbotService.SendMsgToTgbotAdmins(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
web/job/check_hash_storage.go
Normal file
19
web/job/check_hash_storage.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckHashStorageJob struct {
|
||||||
|
tgbotService service.Tgbot
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckHashStorageJob() *CheckHashStorageJob {
|
||||||
|
return new(CheckHashStorageJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here Run is an interface method of the Job interface
|
||||||
|
func (j *CheckHashStorageJob) Run() {
|
||||||
|
// Remove expired hashes from storage
|
||||||
|
j.tgbotService.GetHashStorage().RemoveExpiredHashes()
|
||||||
|
}
|
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",
|
"tgRunTime": "@daily",
|
||||||
"tgBotBackup": "false",
|
"tgBotBackup": "false",
|
||||||
"tgCpu": "0",
|
"tgCpu": "0",
|
||||||
|
"tgLang": "en-US",
|
||||||
"secretEnable": "false",
|
"secretEnable": "false",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,6 +257,10 @@ func (s *SettingService) GetTgCpu() (int, error) {
|
||||||
return s.getInt("tgCpu")
|
return s.getInt("tgCpu")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgLang() (string, error) {
|
||||||
|
return s.getString("tgLang")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetPort() (int, error) {
|
func (s *SettingService) GetPort() (int, error) {
|
||||||
return s.getInt("webPort")
|
return s.getInt("webPort")
|
||||||
}
|
}
|
||||||
|
|
|
@ -603,7 +603,8 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
return fmt.Sprintf("ss://%s@%s:%d#%s", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port, clients[clientIndex].Email)
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, clients[clientIndex].Email)
|
||||||
|
return fmt.Sprintf("ss://%s@%s:%d#%s", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port, remark)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -209,7 +209,7 @@
|
||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "Settings"
|
"title" = "Settings"
|
||||||
"save" = "Save"
|
"save" = "Save"
|
||||||
"infoDesc" = "Every change made here needs to be saved. Please restart the panel for the changes to take effect."
|
"infoDesc" = "Every change made here needs to be saved. Please restart the panel to apply changes."
|
||||||
"restartPanel" = "Restart Panel "
|
"restartPanel" = "Restart Panel "
|
||||||
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please view the panel log information on the server."
|
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please view the panel log information on the server."
|
||||||
"actions" = "Actions"
|
"actions" = "Actions"
|
||||||
|
@ -336,6 +336,8 @@
|
||||||
"manualBlockedDomains" = "List of Blocked Domains"
|
"manualBlockedDomains" = "List of Blocked Domains"
|
||||||
"manualDirectIPs" = "List of Direct IPs"
|
"manualDirectIPs" = "List of Direct IPs"
|
||||||
"manualDirectDomains" = "List of Direct Domains"
|
"manualDirectDomains" = "List of Direct Domains"
|
||||||
|
"manualIPv4Domains" = "List of IPv4 Domains"
|
||||||
|
"manualWARPDomains" = "List of WARP Domains"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Admin"
|
"admin" = "Admin"
|
||||||
|
@ -351,3 +353,115 @@
|
||||||
"modifyUser" = "Modify User "
|
"modifyUser" = "Modify User "
|
||||||
"originalUserPassIncorrect" = "Incorrect original username or password"
|
"originalUserPassIncorrect" = "Incorrect original username or password"
|
||||||
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ Custom keyboard closed!"
|
||||||
|
"noResult" = "❗ No result!"
|
||||||
|
"noQuery" = "❌ Query not found! Please use the command again!"
|
||||||
|
"wentWrong" = "❌ Something went wrong!"
|
||||||
|
"noIpRecord" = "❗ No IP Record!"
|
||||||
|
"noInbounds" = "❗ No inbound found!"
|
||||||
|
"unlimited" = "♾ Unlimited"
|
||||||
|
"month" = "Month"
|
||||||
|
"months" = "Months"
|
||||||
|
"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 }}%"
|
||||||
|
"selectUserFailed" = "❌ Error in user selection!"
|
||||||
|
"userSaved" = "✅ Telegram User saved."
|
||||||
|
"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"
|
||||||
|
"ips" = "🔢 IPs: \r\n{{ .IPs }}\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"
|
||||||
|
"TGUser" = "👤 Telegram User: {{ .TelegramID }}\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"
|
||||||
|
"refreshedOn" = "🔄🕒 Refreshed On: {{ .Time }}\r\n"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ Close Keyboard"
|
||||||
|
"cancel" = "❌ Cancel"
|
||||||
|
"cancelReset" = "❌ Cancel Reset"
|
||||||
|
"cancelIpLimit" = "❌ Cancel IP Limit"
|
||||||
|
"confirmResetTraffic" = "✅ Confirm Reset Traffic?"
|
||||||
|
"confirmClearIps" = "✅ Confirm Clear IPs?"
|
||||||
|
"confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?"
|
||||||
|
"dbBackup" = "Get DB Backup"
|
||||||
|
"serverUsage" = "Server Usage"
|
||||||
|
"getInbounds" = "Get Inbounds"
|
||||||
|
"depleteSoon" = "Deplete soon"
|
||||||
|
"clientUsage" = "Get Usage"
|
||||||
|
"commands" = "Commands"
|
||||||
|
"refresh" = "🔄 Refresh"
|
||||||
|
"clearIPs" = "❌ Clear IPs"
|
||||||
|
"removeTGUser" = "❌ Remove Telegram User"
|
||||||
|
"selectTGUser" = "👤 Select Telegram User"
|
||||||
|
"selectOneTGUser" = "👤 Select a telegram user:"
|
||||||
|
"resetTraffic" = "📈 Reset Traffic"
|
||||||
|
"resetExpire" = "📅 Reset Expire Days"
|
||||||
|
"ipLog" = "🔢 IP Log"
|
||||||
|
"ipLimit" = "🔢 IP Limit"
|
||||||
|
"setTGUser" = "👤 Set Telegram User"
|
||||||
|
"toggle" = "🔘 Enable / Disable"
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"errorOperation" = "❗ Error in Operation."
|
||||||
|
"getInboundsFailed" = "❌ Failed to get inbounds"
|
||||||
|
"canceled" = "❌ {{ .Email }} : Operation canceled."
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }} : Client refreshed successfully."
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }} : IPs refreshed successfully."
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }} : Client's Telegram User refreshed successfully."
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }} : Traffic reset successfully."
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }} : Expire days reset successfully."
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }} : IP limit {{ .Count }} saved successfully."
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }} : IPs cleared successfully."
|
||||||
|
"getIpLog" = "✅ {{ .Email }} : Get IP Log."
|
||||||
|
"getUserInfo" = "✅ {{ .Email }} : Get Telegram User Info."
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }} : Telegram User removed successfully."
|
||||||
|
"enableSuccess" = "✅ {{ .Email }} : Enabled successfully."
|
||||||
|
"disableSuccess" = "✅ {{ .Email }} : Disabled successfully."
|
||||||
|
"askToAddUserId" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram user id in your configuration(s).\r\n\r\nYour user id: <b>{{ .TgUserID }}</b>"
|
||||||
|
"askToAddUserName" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram username or user id in your configuration(s).\r\n\r\nYour username: <b>@{{ .TgUserName }}</b>\r\n\r\nYour user id: <b>{{ .TgUserID }}</b>"
|
||||||
|
|
|
@ -336,6 +336,8 @@
|
||||||
"manualBlockedDomains" = "لیست دامنه های مسدود شده"
|
"manualBlockedDomains" = "لیست دامنه های مسدود شده"
|
||||||
"manualDirectIPs" = "لیست آیپی های مستقیم"
|
"manualDirectIPs" = "لیست آیپی های مستقیم"
|
||||||
"manualDirectDomains" = "لیست دامنه های مستقیم"
|
"manualDirectDomains" = "لیست دامنه های مستقیم"
|
||||||
|
"manualIPv4Domains" = "لیست دامنههای IPv4"
|
||||||
|
"manualWARPDomains" = "لیست دامنه های WARP"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "مدیر"
|
"admin" = "مدیر"
|
||||||
|
@ -351,3 +353,115 @@
|
||||||
"modifyUser" = "ویرایش کاربر"
|
"modifyUser" = "ویرایش کاربر"
|
||||||
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد "
|
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد "
|
||||||
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد "
|
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد "
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!"
|
||||||
|
"noResult" = "❗ نتیجهای یافت نشد!"
|
||||||
|
"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!"
|
||||||
|
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||||
|
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
||||||
|
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||||
|
"unlimited" = "♾ نامحدود"
|
||||||
|
"month" = "ماه"
|
||||||
|
"months" = "ماهها"
|
||||||
|
"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 }}% است."
|
||||||
|
"selectUserFailed" = "❌ خطا در انتخاب کاربر!"
|
||||||
|
"userSaved" = "✅ کاربر تلگرام ذخیره شد."
|
||||||
|
"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"
|
||||||
|
"ips" = "🔢 آدرسهای IP: \r\n{{ .IPs }}\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"
|
||||||
|
"TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n"
|
||||||
|
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
|
||||||
|
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
|
||||||
|
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
|
||||||
|
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
|
||||||
|
"backupTime" = "🗄 زمان پشتیبانگیری: {{ .Time }}\r\n"
|
||||||
|
"refreshedOn" = "🔄🕒 تازهسازی شده در: {{ .Time }}\r\n"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ بستن کیبورد"
|
||||||
|
"cancel" = "❌ لغو"
|
||||||
|
"cancelReset" = "❌ لغو تنظیم مجدد"
|
||||||
|
"cancelIpLimit" = "❌ لغو محدودیت IP"
|
||||||
|
"confirmResetTraffic" = "✅ تأیید تنظیم مجدد ترافیک؟"
|
||||||
|
"confirmClearIps" = "✅ تأیید پاکسازی آدرسهای IP؟"
|
||||||
|
"confirmRemoveTGUser" = "✅ تأیید حذف کاربر تلگرام؟"
|
||||||
|
"dbBackup" = "دریافت پشتیبان پایگاه داده"
|
||||||
|
"serverUsage" = "استفاده از سرور"
|
||||||
|
"getInbounds" = "دریافت ورودیها"
|
||||||
|
"depleteSoon" = "به زودی به پایان خواهد رسید"
|
||||||
|
"clientUsage" = "دریافت آمار کاربر"
|
||||||
|
"commands" = "دستورات"
|
||||||
|
"refresh" = "🔄 تازهسازی"
|
||||||
|
"clearIPs" = "❌ پاکسازی آدرسها"
|
||||||
|
"removeTGUser" = "❌ حذف کاربر تلگرام"
|
||||||
|
"selectTGUser" = "👤 انتخاب کاربر تلگرام"
|
||||||
|
"selectOneTGUser" = "👤 یک کاربر تلگرام را انتخاب کنید:"
|
||||||
|
"resetTraffic" = "📈 تنظیم مجدد ترافیک"
|
||||||
|
"resetExpire" = "📅 تنظیم مجدد تاریخ انقضا"
|
||||||
|
"ipLog" = "🔢 لاگ آدرسهای IP"
|
||||||
|
"ipLimit" = "🔢 محدودیت IP"
|
||||||
|
"setTGUser" = "👤 تنظیم کاربر تلگرام"
|
||||||
|
"toggle" = "🔘 فعال / غیرفعال"
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"errorOperation" = "❗ خطا در عملیات."
|
||||||
|
"getInboundsFailed" = "❌ دریافت ورودیها با خطا مواجه شد."
|
||||||
|
"canceled" = "❌ {{ .Email }} : عملیات لغو شد."
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }} : کلاینت با موفقیت تازهسازی شد."
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }} : آدرسها با موفقیت تازهسازی شدند."
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }} : کاربر تلگرام کلاینت با موفقیت تازهسازی شد."
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }} : ترافیک با موفقیت تنظیم مجدد شد."
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }} : تاریخ انقضا با موفقیت تنظیم مجدد شد."
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }} : محدودیت آدرس IP {{ .Count }} با موفقیت ذخیره شد."
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }} : آدرسها با موفقیت پاکسازی شدند."
|
||||||
|
"getIpLog" = "✅ {{ .Email }} : دریافت لاگ آدرسهای IP."
|
||||||
|
"getUserInfo" = "✅ {{ .Email }} : دریافت اطلاعات کاربر تلگرام."
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }} : کاربر تلگرام با موفقیت حذف شد."
|
||||||
|
"enableSuccess" = "✅ {{ .Email }} : با موفقیت فعال شد."
|
||||||
|
"disableSuccess" = "✅ {{ .Email }} : با موفقیت غیرفعال شد."
|
||||||
|
"askToAddUserId" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: <b>{{ .TgUserID }}</b>"
|
||||||
|
"askToAddUserName" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که نام کاربری یا شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nنام کاربری شما: <b>@{{ .TgUserName }}</b>\r\n\r\nشناسه کاربری شما: <b>{{ .TgUserID }}</b>"
|
||||||
|
|
|
@ -336,6 +336,8 @@
|
||||||
"manualBlockedDomains" = "Список заблокированных доменов"
|
"manualBlockedDomains" = "Список заблокированных доменов"
|
||||||
"manualDirectIPs" = "Список прямых IP адресов"
|
"manualDirectIPs" = "Список прямых IP адресов"
|
||||||
"manualDirectDomains" = "Список прямых доменов"
|
"manualDirectDomains" = "Список прямых доменов"
|
||||||
|
"manualIPv4Domains" = "Список доменов IPv4"
|
||||||
|
"manualWARPDomains" = "Список доменов WARP"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Админ"
|
"admin" = "Админ"
|
||||||
|
@ -351,3 +353,115 @@
|
||||||
"modifyUser" = "Изменение пользователя"
|
"modifyUser" = "Изменение пользователя"
|
||||||
"originalUserPassIncorrect" = "Неверное имя пользователя или пароль"
|
"originalUserPassIncorrect" = "Неверное имя пользователя или пароль"
|
||||||
"userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены"
|
"userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены"
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ Закрыта настраиваемая клавиатура!"
|
||||||
|
"noResult" = "❗ Нет результатов!"
|
||||||
|
"noQuery" = "❌ Запрос не найден! Пожалуйста, повторите команду!"
|
||||||
|
"wentWrong" = "❌ Что-то пошло не так!"
|
||||||
|
"noIpRecord" = "❗ Нет записей об IP-адресе!"
|
||||||
|
"noInbounds" = "❗ Входящих соединений не найдено!"
|
||||||
|
"unlimited" = "♾ Неограниченно"
|
||||||
|
"month" = "Месяц"
|
||||||
|
"months" = "Месяцев"
|
||||||
|
"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 }}%"
|
||||||
|
"selectUserFailed" = "❌ Ошибка при выборе пользователя!"
|
||||||
|
"userSaved" = "✅ Пользователь Telegram сохранен."
|
||||||
|
"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"
|
||||||
|
"ips" = "🔢 IP-адреса: \r\n{{ .IPs }}\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"
|
||||||
|
"TGUser" = "👤 Пользователь Telegram: {{ .TelegramID }}\r\n"
|
||||||
|
"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n"
|
||||||
|
"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n"
|
||||||
|
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
||||||
|
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n \r\n"
|
||||||
|
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
||||||
|
"refreshedOn" = "🔄🕒 Обновлено: {{ .Time }}\r\n"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ Закрыть клавиатуру"
|
||||||
|
"cancel" = "❌ Отмена"
|
||||||
|
"cancelReset" = "❌ Отменить сброс"
|
||||||
|
"cancelIpLimit" = "❌ Отменить лимит IP"
|
||||||
|
"confirmResetTraffic" = "✅ Подтвердить сброс трафика?"
|
||||||
|
"confirmClearIps" = "✅ Подтвердить очистку IP?"
|
||||||
|
"confirmRemoveTGUser" = "✅ Подтвердить удаление пользователя Telegram?"
|
||||||
|
"dbBackup" = "Получить резервную копию DB"
|
||||||
|
"serverUsage" = "Использование сервера"
|
||||||
|
"getInbounds" = "Получить входящие потоки"
|
||||||
|
"depleteSoon" = "Скоро исчерпание"
|
||||||
|
"clientUsage" = "Получить использование"
|
||||||
|
"commands" = "Команды"
|
||||||
|
"refresh" = "🔄 Обновить"
|
||||||
|
"clearIPs" = "❌ Очистить IP"
|
||||||
|
"removeTGUser" = "❌ Удалить пользователя Telegram"
|
||||||
|
"selectTGUser" = "👤 Выбрать пользователя Telegram"
|
||||||
|
"selectOneTGUser" = "👤 Выберите пользователя Telegram:"
|
||||||
|
"resetTraffic" = "📈 Сбросить трафик"
|
||||||
|
"resetExpire" = "📅 Сбросить дату окончания"
|
||||||
|
"ipLog" = "🔢 Лог IP"
|
||||||
|
"ipLimit" = "🔢 Лимит IP"
|
||||||
|
"setTGUser" = "👤 Установить пользователя Telegram"
|
||||||
|
"toggle" = "🔘 Вкл./Выкл."
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"errorOperation" = "❗ Ошибка в операции."
|
||||||
|
"getInboundsFailed" = "❌ Не удалось получить входящие потоки."
|
||||||
|
"canceled" = "❌ {{ .Email }}: Операция отменена."
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }}: Клиент успешно обновлен."
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреса успешно обновлены."
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен."
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }}: Трафик успешно сброшен."
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }}: Дни истечения успешно сброшены."
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен."
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }}: IP-адреса успешно очищены."
|
||||||
|
"getIpLog" = "✅ {{ .Email }}: Получен лог IP."
|
||||||
|
"getUserInfo" = "✅ {{ .Email }}: Получена информация о пользователе Telegram."
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }}: Пользователь Telegram успешно удален."
|
||||||
|
"enableSuccess" = "✅ {{ .Email }}: Включено успешно."
|
||||||
|
"disableSuccess" = "✅ {{ .Email }}: Отключено успешно."
|
||||||
|
"askToAddUserId" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваш идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаш идентификатор пользователя: <b>{{ .TgUserID }}</b>"
|
||||||
|
"askToAddUserName" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваше имя пользователя или идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаше имя пользователя: <b>@{{ .TgUserName }}</b>\r\n\r\nВаш идентификатор пользователя: <b>{{ .TgUserID }}</b>"
|
||||||
|
|
|
@ -336,6 +336,8 @@
|
||||||
"manualBlockedDomains" = "被阻止的域列表"
|
"manualBlockedDomains" = "被阻止的域列表"
|
||||||
"manualDirectIPs" = "直接 IP 列表"
|
"manualDirectIPs" = "直接 IP 列表"
|
||||||
"manualDirectDomains" = "直接域列表"
|
"manualDirectDomains" = "直接域列表"
|
||||||
|
"manualIPv4Domains" = "IPv4 域名列表"
|
||||||
|
"manualWARPDomains" = "WARP域名列表"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "行政"
|
"admin" = "行政"
|
||||||
|
@ -351,3 +353,115 @@
|
||||||
"modifyUser" = "修改用户"
|
"modifyUser" = "修改用户"
|
||||||
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
||||||
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ 自定义键盘已关闭!"
|
||||||
|
"noResult" = "❗ 没有结果!"
|
||||||
|
"noQuery" = "❌ 未找到查询!请重新使用命令!"
|
||||||
|
"wentWrong" = "❌ 出了点问题!"
|
||||||
|
"noIpRecord" = "❗ 没有IP记录!"
|
||||||
|
"noInbounds" = "❗ 没有找到入站连接!"
|
||||||
|
"unlimited" = "♾ 无限制"
|
||||||
|
"month" = "月"
|
||||||
|
"months" = "月"
|
||||||
|
"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 }}%"
|
||||||
|
"selectUserFailed" = "❌ 用户选择错误!"
|
||||||
|
"userSaved" = "✅ 电报用户已保存。"
|
||||||
|
"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"
|
||||||
|
"ips" = "🔢 IP 地址:\r\n{{ .IPs }}\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"
|
||||||
|
"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n"
|
||||||
|
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}:\r\n"
|
||||||
|
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n"
|
||||||
|
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
|
||||||
|
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n \r\n"
|
||||||
|
"backupTime" = "🗄 备份时间:{{ .Time }}\r\n"
|
||||||
|
"refreshedOn" = "🔄🕒 刷新时间:{{ .Time }}\r\n"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ 关闭键盘"
|
||||||
|
"cancel" = "❌ 取消"
|
||||||
|
"cancelReset" = "❌ 取消重置"
|
||||||
|
"cancelIpLimit" = "❌ 取消 IP 限制"
|
||||||
|
"confirmResetTraffic" = "✅ 确认重置流量?"
|
||||||
|
"confirmClearIps" = "✅ 确认清除 IP?"
|
||||||
|
"confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?"
|
||||||
|
"dbBackup" = "获取数据库备份"
|
||||||
|
"serverUsage" = "服务器使用情况"
|
||||||
|
"getInbounds" = "获取入站信息"
|
||||||
|
"depleteSoon" = "即将耗尽"
|
||||||
|
"clientUsage" = "获取使用情况"
|
||||||
|
"commands" = "命令"
|
||||||
|
"refresh" = "🔄 刷新"
|
||||||
|
"clearIPs" = "❌ 清除 IP"
|
||||||
|
"removeTGUser" = "❌ 移除 Telegram 用户"
|
||||||
|
"selectTGUser" = "👤 选择 Telegram 用户"
|
||||||
|
"selectOneTGUser" = "👤 选择一个 Telegram 用户:"
|
||||||
|
"resetTraffic" = "📈 重置流量"
|
||||||
|
"resetExpire" = "📅 重置过期天数"
|
||||||
|
"ipLog" = "🔢 IP 日志"
|
||||||
|
"ipLimit" = "🔢 IP 限制"
|
||||||
|
"setTGUser" = "👤 设置 Telegram 用户"
|
||||||
|
"toggle" = "🔘 启用/禁用"
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"errorOperation" = "❗ 操作错误。"
|
||||||
|
"getInboundsFailed" = "❌ 获取入站信息失败。"
|
||||||
|
"canceled" = "❌ {{ .Email }}:操作已取消。"
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }}:客户端刷新成功。"
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }}:IP 刷新成功。"
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。"
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重置成功。"
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }}:过期天数已重置成功。"
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。"
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }}:IP 已成功清除。"
|
||||||
|
"getIpLog" = "✅ {{ .Email }}:获取 IP 日志。"
|
||||||
|
"getUserInfo" = "✅ {{ .Email }}:获取 Telegram 用户信息。"
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }}:Telegram 用户已成功移除。"
|
||||||
|
"enableSuccess" = "✅ {{ .Email }}:已成功启用。"
|
||||||
|
"disableSuccess" = "✅ {{ .Email }}:已成功禁用。"
|
||||||
|
"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户ID。\r\n\r\n您的用户ID:<b>{{ .TgUserID }}</b>"
|
||||||
|
"askToAddUserName" = "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户名或用户ID。\r\n\r\n您的用户名:<b>@{{ .TgUserName }}</b>\r\n\r\n您的用户ID:<b>{{ .TgUserID }}</b>"
|
||||||
|
|
115
web/web.go
115
web/web.go
|
@ -18,16 +18,14 @@ import (
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
"x-ui/web/controller"
|
"x-ui/web/controller"
|
||||||
"x-ui/web/job"
|
"x-ui/web/job"
|
||||||
|
"x-ui/web/locale"
|
||||||
"x-ui/web/network"
|
"x-ui/web/network"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed assets/*
|
//go:embed assets/*
|
||||||
|
@ -202,13 +200,23 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
c.Header("Cache-Control", "max-age=31536000")
|
c.Header("Cache-Control", "max-age=31536000")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
err = s.initI18n(engine)
|
|
||||||
|
// init i18n
|
||||||
|
err = locale.InitLocalizer(i18nFS, &s.settingService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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() {
|
if config.IsDebug() {
|
||||||
// for develop
|
// for development
|
||||||
files, err := s.getHtmlFiles()
|
files, err := s.getHtmlFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -216,12 +224,12 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
engine.LoadHTMLFiles(files...)
|
engine.LoadHTMLFiles(files...)
|
||||||
engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets")))
|
engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets")))
|
||||||
} else {
|
} else {
|
||||||
// for prod
|
// for production
|
||||||
t, err := s.getHtmlTemplate(engine.FuncMap)
|
template, err := s.getHtmlTemplate(engine.FuncMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
engine.SetHTMLTemplate(t)
|
engine.SetHTMLTemplate(template)
|
||||||
engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS}))
|
engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,87 +247,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
return engine, nil
|
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) {
|
|
||||||
//accept := c.GetHeader("Accept-Language")
|
|
||||||
|
|
||||||
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() {
|
func (s *Server) startTask() {
|
||||||
err := s.xrayService.RestartXray(true)
|
err := s.xrayService.RestartXray(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -346,7 +273,7 @@ func (s *Server) startTask() {
|
||||||
if (err == nil) && (isTgbotenabled) {
|
if (err == nil) && (isTgbotenabled) {
|
||||||
runtime, err := s.settingService.GetTgbotRuntime()
|
runtime, err := s.settingService.GetTgbotRuntime()
|
||||||
if err != nil || runtime == "" {
|
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"
|
runtime = "@daily"
|
||||||
}
|
}
|
||||||
logger.Infof("Tg notify enabled,run at %s", runtime)
|
logger.Infof("Tg notify enabled,run at %s", runtime)
|
||||||
|
@ -356,12 +283,14 @@ func (s *Server) startTask() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for Telegram bot callback query hash storage reset
|
||||||
|
s.cron.AddJob("@every 2m", job.NewCheckHashStorageJob())
|
||||||
|
|
||||||
// Check CPU load and alarm to TgBot if threshold passes
|
// Check CPU load and alarm to TgBot if threshold passes
|
||||||
cpuThreshold, err := s.settingService.GetTgCpu()
|
cpuThreshold, err := s.settingService.GetTgCpu()
|
||||||
if (err == nil) && (cpuThreshold > 0) {
|
if (err == nil) && (cpuThreshold > 0) {
|
||||||
s.cron.AddJob("@every 10s", job.NewCheckCpuJob())
|
s.cron.AddJob("@every 10s", job.NewCheckCpuJob())
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
s.cron.Remove(entry)
|
s.cron.Remove(entry)
|
||||||
}
|
}
|
||||||
|
@ -441,7 +370,7 @@ func (s *Server) Start() (err error) {
|
||||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||||
if (err == nil) && (isTgbotenabled) {
|
if (err == nil) && (isTgbotenabled) {
|
||||||
tgBot := s.tgbotService.NewTgbot()
|
tgBot := s.tgbotService.NewTgbot()
|
||||||
tgBot.Start()
|
tgBot.Start(i18nFS)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -453,7 +382,7 @@ func (s *Server) Stop() error {
|
||||||
if s.cron != nil {
|
if s.cron != nil {
|
||||||
s.cron.Stop()
|
s.cron.Stop()
|
||||||
}
|
}
|
||||||
if s.tgbotService.IsRunnging() {
|
if s.tgbotService.IsRunning() {
|
||||||
s.tgbotService.Stop()
|
s.tgbotService.Stop()
|
||||||
}
|
}
|
||||||
var err1 error
|
var err1 error
|
||||||
|
|
Loading…
Reference in a new issue