diff options
45 files changed, 212 insertions, 201 deletions
@@ -3,6 +3,7 @@ .cache .sync* *.tar.gz +*.log access.log error.log tmp @@ -311,9 +311,9 @@ If you want to use routing to WARP before v2.1.0 follow steps as below: ```sh "log": { - "access": "./access.log", - "dnsLog": false, - "loglevel": "warning" + "access": "./access.log", + "dnsLog": false, + "loglevel": "warning" }, ``` diff --git a/database/model/model.go b/database/model/model.go index 32ab255f..df41237d 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -2,6 +2,7 @@ package model import ( "fmt" + "x-ui/util/json_util" "x-ui/xray" ) diff --git a/logger/logger.go b/logger/logger.go index ca047cbc..35c5c0ac 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -8,12 +8,14 @@ import ( "github.com/op/go-logging" ) -var logger *logging.Logger -var logBuffer []struct { - time string - level logging.Level - log string -} +var ( + logger *logging.Logger + logBuffer []struct { + time string + level logging.Level + log string + } +) func init() { InitLogger(logging.INFO) @@ -8,6 +8,7 @@ import ( "os/signal" "syscall" _ "unsafe" + "x-ui/config" "x-ui/database" "x-ui/logger" @@ -7,6 +7,7 @@ import ( "net" "net/http" "strconv" + "x-ui/config" "x-ui/logger" "x-ui/util/common" @@ -99,7 +100,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { } func (s *Server) Start() (err error) { - //This is an anonymous function, no function name + // This is an anonymous function, no function name defer func() { if err != nil { s.Stop() diff --git a/sub/subController.go b/sub/subController.go index b5c5cbac..8de5b5df 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -25,8 +25,8 @@ func NewSUBController( showInfo bool, rModel string, update string, - jsonFragment string) *SUBController { - + jsonFragment string, +) *SUBController { a := &SUBController{ subPath: subPath, subJsonPath: jsonPath, diff --git a/sub/subJsonService.go b/sub/subJsonService.go index 8bc98dea..9320306e 100644 --- a/sub/subJsonService.go +++ b/sub/subJsonService.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "strings" + "x-ui/database/model" "x-ui/logger" "x-ui/util/json_util" diff --git a/sub/subService.go b/sub/subService.go index 06d1ed0a..5363149a 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -6,6 +6,7 @@ import ( "net/url" "strings" "time" + "x-ui/database" "x-ui/database/model" "x-ui/logger" diff --git a/util/common/err.go b/util/common/err.go index f6078c33..494e5c82 100644 --- a/util/common/err.go +++ b/util/common/err.go @@ -3,6 +3,7 @@ package common import ( "errors" "fmt" + "x-ui/logger" ) diff --git a/util/random/random.go b/util/random/random.go index 1dd47ec9..67ee0691 100644 --- a/util/random/random.go +++ b/util/random/random.go @@ -4,12 +4,14 @@ import ( "math/rand" ) -var numSeq [10]rune -var lowerSeq [26]rune -var upperSeq [26]rune -var numLowerSeq [36]rune -var numUpperSeq [36]rune -var allSeq [62]rune +var ( + numSeq [10]rune + lowerSeq [26]rune + upperSeq [26]rune + numLowerSeq [36]rune + numUpperSeq [36]rune + allSeq [62]rune +) func init() { for i := 0; i < 10; i++ { diff --git a/web/assets/js/axios-init.js b/web/assets/js/axios-init.js index b864b714..f0b0f4be 100644 --- a/web/assets/js/axios-init.js +++ b/web/assets/js/axios-init.js @@ -14,3 +14,17 @@ axios.interceptors.request.use( }, (error) => Promise.reject(error), ); + +axios.interceptors.response.use( + (response) => response, + (error) => { + if (error.response) { + const statusCode = error.response.status; + // Check the status code + if (statusCode === 401) { // Unauthorized + return window.location.reload(); + } + } + return Promise.reject(error); + } +); diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js index 61b322bd..f2f05f01 100644 --- a/web/assets/js/util/utils.js +++ b/web/assets/js/util/utils.js @@ -131,11 +131,11 @@ class RandomUtil { static randomUUID() { const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; return template.replace(/[xy]/g, function (c) { - const randomValues = new Uint8Array(1); - crypto.getRandomValues(randomValues); - let randomValue = randomValues[0] % 16; - let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8); - return calculatedValue.toString(16); + const randomValues = new Uint8Array(1); + crypto.getRandomValues(randomValues); + let randomValue = randomValues[0] % 16; + let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8); + return calculatedValue.toString(16); }); } diff --git a/web/controller/api.go b/web/controller/api.go index 73c36787..6281097b 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -22,91 +22,37 @@ func (a *APIController) initRouter(g *gin.RouterGroup) { g = g.Group("/panel/api/inbounds") g.Use(a.checkLogin) - g.GET("/list", a.getAllInbounds) - g.GET("/get/:id", a.getSingleInbound) - g.GET("/getClientTraffics/:email", a.getClientTraffics) - g.POST("/add", a.addInbound) - g.POST("/del/:id", a.delInbound) - g.POST("/update/:id", a.updateInbound) - g.POST("/clientIps/:email", a.getClientIps) - g.POST("/clearClientIps/:email", a.clearClientIps) - g.POST("/addClient", a.addInboundClient) - g.POST("/:id/delClient/:clientId", a.delInboundClient) - g.POST("/updateClient/:clientId", a.updateInboundClient) - g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) - g.POST("/resetAllTraffics", a.resetAllTraffics) - g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) - g.POST("/delDepletedClients/:id", a.delDepletedClients) - g.GET("/createbackup", a.createBackup) - g.POST("/onlines", a.onlines) - a.inboundController = NewInboundController(g) -} - -func (a *APIController) getAllInbounds(c *gin.Context) { - a.inboundController.getInbounds(c) -} - -func (a *APIController) getSingleInbound(c *gin.Context) { - a.inboundController.getInbound(c) -} - -func (a *APIController) getClientTraffics(c *gin.Context) { - a.inboundController.getClientTraffics(c) -} - -func (a *APIController) addInbound(c *gin.Context) { - a.inboundController.addInbound(c) -} - -func (a *APIController) delInbound(c *gin.Context) { - a.inboundController.delInbound(c) -} - -func (a *APIController) updateInbound(c *gin.Context) { - a.inboundController.updateInbound(c) -} - -func (a *APIController) getClientIps(c *gin.Context) { - a.inboundController.getClientIps(c) -} -func (a *APIController) clearClientIps(c *gin.Context) { - a.inboundController.clearClientIps(c) -} - -func (a *APIController) addInboundClient(c *gin.Context) { - a.inboundController.addInboundClient(c) -} - -func (a *APIController) delInboundClient(c *gin.Context) { - a.inboundController.delInboundClient(c) -} - -func (a *APIController) updateInboundClient(c *gin.Context) { - a.inboundController.updateInboundClient(c) -} - -func (a *APIController) resetClientTraffic(c *gin.Context) { - a.inboundController.resetClientTraffic(c) -} - -func (a *APIController) resetAllTraffics(c *gin.Context) { - a.inboundController.resetAllTraffics(c) -} - -func (a *APIController) resetAllClientTraffics(c *gin.Context) { - a.inboundController.resetAllClientTraffics(c) -} - -func (a *APIController) delDepletedClients(c *gin.Context) { - a.inboundController.delDepletedClients(c) + inboundRoutes := []struct { + Method string + Path string + Handler gin.HandlerFunc + }{ + {"GET", "/createbackup", a.createBackup}, + {"GET", "/list", a.inboundController.getInbounds}, + {"GET", "/get/:id", a.inboundController.getInbound}, + {"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics}, + {"POST", "/add", a.inboundController.addInbound}, + {"POST", "/del/:id", a.inboundController.delInbound}, + {"POST", "/update/:id", a.inboundController.updateInbound}, + {"POST", "/clientIps/:email", a.inboundController.getClientIps}, + {"POST", "/clearClientIps/:email", a.inboundController.clearClientIps}, + {"POST", "/addClient", a.inboundController.addInboundClient}, + {"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient}, + {"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient}, + {"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic}, + {"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics}, + {"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics}, + {"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients}, + {"POST", "/onlines", a.inboundController.onlines}, + } + + for _, route := range inboundRoutes { + g.Handle(route.Method, route.Path, route.Handler) + } } func (a *APIController) createBackup(c *gin.Context) { a.Tgbot.SendBackupToAdmins() } - -func (a *APIController) onlines(c *gin.Context) { - a.inboundController.onlines(c) -} diff --git a/web/controller/base.go b/web/controller/base.go index 674a195d..492fc2dc 100644 --- a/web/controller/base.go +++ b/web/controller/base.go @@ -2,6 +2,7 @@ package controller import ( "net/http" + "x-ui/logger" "x-ui/web/locale" "x-ui/web/session" @@ -9,13 +10,12 @@ import ( "github.com/gin-gonic/gin" ) -type BaseController struct { -} +type BaseController struct{} func (a *BaseController) checkLogin(c *gin.Context) { if !session.IsLogin(c) { if isAjax(c) { - pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain")) + pureJsonMsg(c, http.StatusUnauthorized, false, I18nWeb(c, "pages.login.loginAgain")) } else { c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) } diff --git a/web/controller/inbound.go b/web/controller/inbound.go index d613453f..7ef3245f 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "strconv" + "x-ui/database/model" "x-ui/web/service" "x-ui/web/session" diff --git a/web/controller/index.go b/web/controller/index.go index 9be88273..bc3c4204 100644 --- a/web/controller/index.go +++ b/web/controller/index.go @@ -3,6 +3,7 @@ package controller import ( "net/http" "time" + "x-ui/logger" "x-ui/web/service" "x-ui/web/session" @@ -49,15 +50,15 @@ func (a *IndexController) login(c *gin.Context) { var form LoginForm err := c.ShouldBind(&form) if err != nil { - pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData")) + pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData")) return } if form.Username == "" { - pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername")) + pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyUsername")) return } if form.Password == "" { - pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword")) + pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyPassword")) return } @@ -66,7 +67,7 @@ func (a *IndexController) login(c *gin.Context) { if user == nil { logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password) a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) - pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) + pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) return } else { logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c)) diff --git a/web/controller/server.go b/web/controller/server.go index 10baacb9..0eeca71c 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -5,6 +5,7 @@ import ( "net/http" "regexp" "time" + "x-ui/web/global" "x-ui/web/service" diff --git a/web/controller/setting.go b/web/controller/setting.go index 64cae71b..d04969dc 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -3,6 +3,7 @@ package controller import ( "errors" "time" + "x-ui/web/entity" "x-ui/web/service" "x-ui/web/session" diff --git a/web/controller/util.go b/web/controller/util.go index da77189b..a32c9270 100644 --- a/web/controller/util.go +++ b/web/controller/util.go @@ -4,6 +4,7 @@ import ( "net" "net/http" "strings" + "x-ui/config" "x-ui/logger" "x-ui/web/entity" @@ -48,18 +49,11 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) { c.JSON(http.StatusOK, m) } -func pureJsonMsg(c *gin.Context, success bool, msg string) { - if success { - c.JSON(http.StatusOK, entity.Msg{ - Success: true, - Msg: msg, - }) - } else { - c.JSON(http.StatusOK, entity.Msg{ - Success: false, - Msg: msg, - }) - } +func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) { + c.JSON(statusCode, entity.Msg{ + Success: success, + Msg: msg, + }) } func html(c *gin.Context, name string, title string, data gin.H) { diff --git a/web/entity/entity.go b/web/entity/entity.go index 06850128..0a9c3a56 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -5,6 +5,7 @@ import ( "net" "strings" "time" + "x-ui/util/common" ) diff --git a/web/global/global.go b/web/global/global.go index 7d0b4e1f..e92c375b 100644 --- a/web/global/global.go +++ b/web/global/global.go @@ -7,8 +7,10 @@ import ( "github.com/robfig/cron/v3" ) -var webServer WebServer -var subServer SubServer +var ( + webServer WebServer + subServer SubServer +) type WebServer interface { GetCron() *cron.Cron diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html index 0e9f15e4..20a36f63 100644 --- a/web/html/xui/xray.html +++ b/web/html/xui/xray.html @@ -180,7 +180,7 @@ <a-col :lg="24" :xl="12"> <template> <a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%"> - <a-select-option v-for="s in access" :value="s">[[ s ]]</a-select-option> + <a-select-option v-for="s in access" :key="s" :value="s">[[ s ]]</a-select-option> </a-select> </template> </a-col> @@ -193,7 +193,7 @@ <a-col :lg="24" :xl="12"> <template> <a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%"> - <a-select-option v-for="s in error" :value="s">[[ s ]]</a-select-option> + <a-select-option v-for="s in error" :key="s" :value="s">[[ s ]]</a-select-option> </a-select> </template> </a-col> @@ -765,8 +765,8 @@ }, routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"], logLevel: ["none" , "debug" , "info" , "warning", "error"], - access: ["none" , "./access.log" ], - error: ["none" , "./error.log" ], + access: [], + error: [], settingsData: { protocols: { bittorrent: ["bittorrent"], @@ -869,10 +869,10 @@ }, async getXrayResult() { const msg = await HttpUtil.get("/panel/xray/getXrayResult"); - if(msg.success){ - this.restartResult=msg.obj; - if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj); - } + if (msg.success) { + this.restartResult=msg.obj; + if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj); + } }, async fetchUserSecret() { this.loading(true); @@ -910,9 +910,9 @@ }, async toggleToken(value) { if (value) { - await this.getNewSecret(); + await this.getNewSecret(); } else { - this.user.loginSecret = ""; + this.user.loginSecret = ""; } }, async resetXrayConfigToDefault() { @@ -1001,7 +1001,7 @@ this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions); this.cm.on('change',editor => { value = editor.getValue(); - if(this.isJsonString(value)){ + if (this.isJsonString(value)) { this[this.advSettings] = value; } }); @@ -1403,8 +1403,24 @@ }, computed: { templateSettings: { - get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; }, - set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); }, + get: function () { + const parsedSettings = this.xraySetting ? JSON.parse(this.xraySetting) : null; + let accessLogPath = "./access.log"; + let errorLogPath = "./error.log"; + if (parsedSettings) { + // if its set to "none" add default value + if (parsedSettings.log.access !== "none") accessLogPath = parsedSettings.log.access; + if (parsedSettings.log.error !== "none") errorLogPath = parsedSettings.log.error; + } + this.access = ["none", accessLogPath]; + this.error = ["none", errorLogPath]; + return parsedSettings; + }, + set: function (newValue) { + this.xraySetting = JSON.stringify(newValue, null, 2); + this.access = ["none", newValue.log.access]; + this.error = ["none", newValue.log.error]; + }, }, inboundSettings: { get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; }, diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go index 344160e1..4b799ab1 100644 --- a/web/job/check_client_ip_job.go +++ b/web/job/check_client_ip_job.go @@ -35,35 +35,27 @@ func (j *CheckClientIpJob) Run() { j.lastClear = time.Now().Unix() } + shouldClearAccessLog := false f2bInstalled := j.checkFail2BanInstalled() - accessLogPath := xray.GetAccessLogPath() - clearAccessLog := false + isAccessLogAvailable := j.checkAccessLogAvailable(f2bInstalled) if j.hasLimitIp() { - if f2bInstalled && accessLogPath == "./access.log" { - clearAccessLog = j.processLogFile() + if f2bInstalled && isAccessLogAvailable { + shouldClearAccessLog = j.processLogFile() } else { if !f2bInstalled { logger.Warning("fail2ban is not installed. IP limiting may not work properly.") } - switch accessLogPath { - case "none": - logger.Warning("Access log is set to 'none', check your Xray Configs") - case "": - logger.Warning("Access log doesn't exist in your Xray Configs") - default: - logger.Warning("Current access.log path is not compatible with IP Limit") - } } } - if clearAccessLog || accessLogPath == "./access.log" && time.Now().Unix() - j.lastClear > 3600 { + if shouldClearAccessLog || isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600 { j.clearAccessLog() } } func (j *CheckClientIpJob) clearAccessLog() { - logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) j.checkError(err) // reopen the access log file for reading @@ -178,6 +170,25 @@ func (j *CheckClientIpJob) processLogFile() bool { return shouldCleanLog } +func (j *CheckClientIpJob) checkAccessLogAvailable(doWarning bool) bool { + accessLogPath := xray.GetAccessLogPath() + isAvailable := true + warningMsg := "" + // access log is not available if it is set to 'none' or an empty string + switch accessLogPath { + case "none": + warningMsg = "Access log is set to 'none', check your Xray Configs" + isAvailable = false + case "": + warningMsg = "Access log doesn't exist in your Xray Configs" + isAvailable = false + } + if doWarning && warningMsg != "" { + logger.Warning(warningMsg) + } + return isAvailable +} + func (j *CheckClientIpJob) checkError(e error) { if e != nil { logger.Warning("client ip job err:", e) @@ -253,7 +264,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun j.disAllowedIps = []string{} // create iplimit log file channel - logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { logger.Errorf("failed to
|
