From b73e4173a3c1e69e02ad6b4e3b43e425e57a5be9 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 9 Feb 2023 22:48:06 +0330 Subject: 3x-ui --- web/controller/api.go | 48 ++++++++++++++++ web/controller/base.go | 33 +++++++++++ web/controller/inbound.go | 136 ++++++++++++++++++++++++++++++++++++++++++++++ web/controller/index.go | 84 ++++++++++++++++++++++++++++ web/controller/server.go | 85 +++++++++++++++++++++++++++++ web/controller/setting.go | 88 ++++++++++++++++++++++++++++++ web/controller/util.go | 97 +++++++++++++++++++++++++++++++++ web/controller/xui.go | 42 ++++++++++++++ 8 files changed, 613 insertions(+) create mode 100644 web/controller/api.go create mode 100644 web/controller/base.go create mode 100644 web/controller/inbound.go create mode 100644 web/controller/index.go create mode 100644 web/controller/server.go create mode 100644 web/controller/setting.go create mode 100644 web/controller/util.go create mode 100644 web/controller/xui.go (limited to 'web/controller') diff --git a/web/controller/api.go b/web/controller/api.go new file mode 100644 index 00000000..84ac9c20 --- /dev/null +++ b/web/controller/api.go @@ -0,0 +1,48 @@ +package controller + +import ( + "github.com/gin-gonic/gin" +) +type APIController struct { + BaseController + + inboundController *InboundController + settingController *SettingController +} + +func NewAPIController(g *gin.RouterGroup) *APIController { + a := &APIController{} + a.initRouter(g) + return a +} + +func (a *APIController) initRouter(g *gin.RouterGroup) { + g = g.Group("/xui/API/inbounds") + g.Use(a.checkLogin) + + g.GET("/", a.inbounds) + g.GET("/get/:id", a.inbound) + g.POST("/add", a.addInbound) + g.POST("/del/:id", a.delInbound) + g.POST("/update/:id", a.updateInbound) + + + a.inboundController = NewInboundController(g) +} + + +func (a *APIController) inbounds(c *gin.Context) { + a.inboundController.getInbounds(c) +} +func (a *APIController) inbound(c *gin.Context) { + a.inboundController.getInbound(c) +} +func (a *APIController) 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) +} diff --git a/web/controller/base.go b/web/controller/base.go new file mode 100644 index 00000000..6ed2f0ef --- /dev/null +++ b/web/controller/base.go @@ -0,0 +1,33 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "net/http" + "x-ui/web/session" +) + +type BaseController struct { +} + +func (a *BaseController) checkLogin(c *gin.Context) { + if !session.IsLogin(c) { + if isAjax(c) { + pureJsonMsg(c, false, I18n(c , "pages.login.loginAgain")) + } else { + c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) + } + c.Abort() + } else { + c.Next() + } +} + + +func I18n(c *gin.Context , name string) string{ + anyfunc, _ := c.Get("I18n") + i18n, _ := anyfunc.(func(key string, params ...string) (string, error)) + + message, _ := i18n(name) + + return message; +} diff --git a/web/controller/inbound.go b/web/controller/inbound.go new file mode 100644 index 00000000..7be877e3 --- /dev/null +++ b/web/controller/inbound.go @@ -0,0 +1,136 @@ +package controller + +import ( + "fmt" + "github.com/gin-gonic/gin" + "strconv" + "x-ui/database/model" + "x-ui/logger" + "x-ui/web/global" + "x-ui/web/service" + "x-ui/web/session" +) + +type InboundController struct { + inboundService service.InboundService + xrayService service.XrayService +} + +func NewInboundController(g *gin.RouterGroup) *InboundController { + a := &InboundController{} + a.initRouter(g) + a.startTask() + return a +} + +func (a *InboundController) initRouter(g *gin.RouterGroup) { + g = g.Group("/inbound") + + g.POST("/list", a.getInbounds) + g.POST("/add", a.addInbound) + g.POST("/del/:id", a.delInbound) + g.POST("/update/:id", a.updateInbound) + + g.POST("/resetClientTraffic/:email", a.resetClientTraffic) + + +} + +func (a *InboundController) startTask() { + webServer := global.GetWebServer() + c := webServer.GetCron() + c.AddFunc("@every 10s", func() { + if a.xrayService.IsNeedRestartAndSetFalse() { + err := a.xrayService.RestartXray(false) + if err != nil { + logger.Error("restart xray failed:", err) + } + } + }) +} + +func (a *InboundController) getInbounds(c *gin.Context) { + user := session.GetLoginUser(c) + inbounds, err := a.inboundService.GetInbounds(user.Id) + if err != nil { + jsonMsg(c, I18n(c , "pages.inbounds.toasts.obtain"), err) + return + } + jsonObj(c, inbounds, nil) +} +func (a *InboundController) getInbound(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18n(c , "get"), err) + return + } + inbound, err := a.inboundService.GetInbound(id) + if err != nil { + jsonMsg(c, I18n(c , "pages.inbounds.toasts.obtain"), err) + return + } + jsonObj(c, inbound, nil) +} + +func (a *InboundController) addInbound(c *gin.Context) { + inbound := &model.Inbound{} + err := c.ShouldBind(inbound) + if err != nil { + jsonMsg(c, I18n(c , "pages.inbounds.addTo"), err) + return + } + user := session.GetLoginUser(c) + inbound.UserId = user.Id + inbound.Enable = true + inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) + inbound, err = a.inboundService.AddInbound(inbound) + jsonMsgObj(c, I18n(c , "pages.inbounds.addTo"), inbound, err) + if err == nil { + a.xrayService.SetToNeedRestart() + } +} + +func (a *InboundController) delInbound(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18n(c , "delete"), err) + return + } + err = a.inboundService.DelInbound(id) + jsonMsgObj(c, I18n(c , "delete"), id, err) + if err == nil { + a.xrayService.SetToNeedRestart() + } +} + +func (a *InboundController) updateInbound(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18n(c , "pages.inbounds.revise"), err) + return + } + inbound := &model.Inbound{ + Id: id, + } + err = c.ShouldBind(inbound) + if err != nil { + jsonMsg(c, I18n(c , "pages.inbounds.revise"), err) + return + } + inbound, err = a.inboundService.UpdateInbound(inbound) + jsonMsgObj(c, I18n(c , "pages.inbounds.revise"), inbound, err) + if err == nil { + a.xrayService.SetToNeedRestart() + } +} + +func (a *InboundController) resetClientTraffic(c *gin.Context) { + email := c.Param("email") + + err := a.inboundService.ResetClientTraffic(email) + if err != nil { + jsonMsg(c, "something worng!", err) + return + } + jsonMsg(c, "traffic reseted", nil) +} diff --git a/web/controller/index.go b/web/controller/index.go new file mode 100644 index 00000000..e0be6076 --- /dev/null +++ b/web/controller/index.go @@ -0,0 +1,84 @@ +package controller + +import ( + "net/http" + "time" + "x-ui/logger" + "x-ui/web/job" + "x-ui/web/service" + "x-ui/web/session" + + "github.com/gin-gonic/gin" +) + +type LoginForm struct { + Username string `json:"username" form:"username"` + Password string `json:"password" form:"password"` +} + +type IndexController struct { + BaseController + + userService service.UserService +} + +func NewIndexController(g *gin.RouterGroup) *IndexController { + a := &IndexController{} + a.initRouter(g) + return a +} + +func (a *IndexController) initRouter(g *gin.RouterGroup) { + g.GET("/", a.index) + g.POST("/login", a.login) + g.GET("/logout", a.logout) +} + +func (a *IndexController) index(c *gin.Context) { + if session.IsLogin(c) { + c.Redirect(http.StatusTemporaryRedirect, "xui/") + return + } + html(c, "login.html", "pages.login.title", nil) +} + +func (a *IndexController) login(c *gin.Context) { + var form LoginForm + err := c.ShouldBind(&form) + if err != nil { + pureJsonMsg(c, false, I18n(c , "pages.login.toasts.invalidFormData")) + return + } + if form.Username == "" { + pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyUsername")) + return + } + if form.Password == "" { + pureJsonMsg(c, false, I18n(c , "pages.login.toasts.emptyPassword")) + return + } + user := a.userService.CheckUser(form.Username, form.Password) + timeStr := time.Now().Format("2006-01-02 15:04:05") + if user == nil { + job.NewStatsNotifyJob().UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) + logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password) + pureJsonMsg(c, false, I18n(c , "pages.login.toasts.wrongUsernameOrPassword")) + return + } else { + logger.Infof("%s login success,Ip Address:%s\n", form.Username, getRemoteIp(c)) + job.NewStatsNotifyJob().UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1) + } + + err = session.SetLoginUser(c, user) + logger.Info("user", user.Id, "login success") + jsonMsg(c, I18n(c , "pages.login.toasts.successLogin"), err) +} + +func (a *IndexController) logout(c *gin.Context) { + user := session.GetLoginUser(c) + if user != nil { + logger.Info("user", user.Id, "logout") + } + session.ClearSession(c) + c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) +} diff --git a/web/controller/server.go b/web/controller/server.go new file mode 100644 index 00000000..d59b3400 --- /dev/null +++ b/web/controller/server.go @@ -0,0 +1,85 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "time" + "x-ui/web/global" + "x-ui/web/service" +) + +type ServerController struct { + BaseController + + serverService service.ServerService + + lastStatus *service.Status + lastGetStatusTime time.Time + + lastVersions []string + lastGetVersionsTime time.Time +} + +func NewServerController(g *gin.RouterGroup) *ServerController { + a := &ServerController{ + lastGetStatusTime: time.Now(), + } + a.initRouter(g) + a.startTask() + return a +} + +func (a *ServerController) initRouter(g *gin.RouterGroup) { + g = g.Group("/server") + + g.Use(a.checkLogin) + g.POST("/status", a.status) + g.POST("/getXrayVersion", a.getXrayVersion) + g.POST("/installXray/:version", a.installXray) +} + +func (a *ServerController) refreshStatus() { + a.lastStatus = a.serverService.GetStatus(a.lastStatus) +} + +func (a *ServerController) startTask() { + webServer := global.GetWebServer() + c := webServer.GetCron() + c.AddFunc("@every 2s", func() { + now := time.Now() + if now.Sub(a.lastGetStatusTime) > time.Minute*3 { + return + } + a.refreshStatus() + }) +} + +func (a *ServerController) status(c *gin.Context) { + a.lastGetStatusTime = time.Now() + + jsonObj(c, a.lastStatus, nil) +} + +func (a *ServerController) getXrayVersion(c *gin.Context) { + now := time.Now() + if now.Sub(a.lastGetVersionsTime) <= time.Minute { + jsonObj(c, a.lastVersions, nil) + return + } + + versions, err := a.serverService.GetXrayVersions() + if err != nil { + jsonMsg(c, I18n(c , "getVersion"), err) + return + } + + a.lastVersions = versions + a.lastGetVersionsTime = time.Now() + + jsonObj(c, versions, nil) +} + +func (a *ServerController) installXray(c *gin.Context) { + version := c.Param("version") + err := a.serverService.UpdateXray(version) + jsonMsg(c, I18n(c , "install") + " xray", err) +} diff --git a/web/controller/setting.go b/web/controller/setting.go new file mode 100644 index 00000000..f500c76d --- /dev/null +++ b/web/controller/setting.go @@ -0,0 +1,88 @@ +package controller + +import ( + "errors" + "github.com/gin-gonic/gin" + "time" + "x-ui/web/entity" + "x-ui/web/service" + "x-ui/web/session" +) + +type updateUserForm struct { + OldUsername string `json:"oldUsername" form:"oldUsername"` + OldPassword string `json:"oldPassword" form:"oldPassword"` + NewUsername string `json:"newUsername" form:"newUsername"` + NewPassword string `json:"newPassword" form:"newPassword"` +} + +type SettingController struct { + settingService service.SettingService + userService service.UserService + panelService service.PanelService +} + +func NewSettingController(g *gin.RouterGroup) *SettingController { + a := &SettingController{} + a.initRouter(g) + return a +} + +func (a *SettingController) initRouter(g *gin.RouterGroup) { + g = g.Group("/setting") + + g.POST("/all", a.getAllSetting) + g.POST("/update", a.updateSetting) + g.POST("/updateUser", a.updateUser) + g.POST("/restartPanel", a.restartPanel) +} + +func (a *SettingController) getAllSetting(c *gin.Context) { + allSetting, err := a.settingService.GetAllSetting() + if err != nil { + jsonMsg(c, I18n(c , "pages.setting.toasts.getSetting"), err) + return + } + jsonObj(c, allSetting, nil) +} + +func (a *SettingController) updateSetting(c *gin.Context) { + allSetting := &entity.AllSetting{} + err := c.ShouldBind(allSetting) + if err != nil { + jsonMsg(c, I18n(c , "pages.setting.toasts.modifySetting"), err) + return + } + err = a.settingService.UpdateAllSetting(allSetting) + jsonMsg(c, I18n(c , "pages.setting.toasts.modifySetting"), err) +} + +func (a *SettingController) updateUser(c *gin.Context) { + form := &updateUserForm{} + err := c.ShouldBind(form) + if err != nil { + jsonMsg(c, I18n(c , "pages.setting.toasts.modifySetting"), err) + return + } + user := session.GetLoginUser(c) + if user.Username != form.OldUsername || user.Password != form.OldPassword { + jsonMsg(c, I18n(c , "pages.setting.toasts.modifyUser"), errors.New(I18n(c , "pages.setting.toasts.originalUserPassIncorrect"))) + return + } + if form.NewUsername == "" || form.NewPassword == "" { + jsonMsg(c,I18n(c , "pages.setting.toasts.modifyUser"), errors.New(I18n(c , "pages.setting.toasts.userPassMustBeNotEmpty"))) + return + } + err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword) + if err == nil { + user.Username = form.NewUsername + user.Password = form.NewPassword + session.SetLoginUser(c, user) + } + jsonMsg(c, I18n(c , "pages.setting.toasts.modifyUser"), err) +} + +func (a *SettingController) restartPanel(c *gin.Context) { + err := a.panelService.RestartPanel(time.Second * 3) + jsonMsg(c, I18n(c , "pages.setting.restartPanel"), err) +} diff --git a/web/controller/util.go b/web/controller/util.go new file mode 100644 index 00000000..2bd2fa55 --- /dev/null +++ b/web/controller/util.go @@ -0,0 +1,97 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "net" + "net/http" + "strings" + "x-ui/config" + "x-ui/logger" + "x-ui/web/entity" +) + +func getUriId(c *gin.Context) int64 { + s := struct { + Id int64 `uri:"id"` + }{} + + _ = c.BindUri(&s) + return s.Id +} + +func getRemoteIp(c *gin.Context) string { + value := c.GetHeader("X-Forwarded-For") + if value != "" { + ips := strings.Split(value, ",") + return ips[0] + } else { + addr := c.Request.RemoteAddr + ip, _, _ := net.SplitHostPort(addr) + return ip + } +} + +func jsonMsg(c *gin.Context, msg string, err error) { + jsonMsgObj(c, msg, nil, err) +} + +func jsonObj(c *gin.Context, obj interface{}, err error) { + jsonMsgObj(c, "", obj, err) +} + +func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) { + m := entity.Msg{ + Obj: obj, + } + if err == nil { + m.Success = true + if msg != "" { + m.Msg = msg + I18n(c , "success") + } + } else { + m.Success = false + m.Msg = msg + I18n(c , "fail") + ": " + err.Error() + logger.Warning(msg + I18n(c , "fail") + ": ", err) + } + 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 html(c *gin.Context, name string, title string, data gin.H) { + if data == nil { + data = gin.H{} + } + data["title"] = title + data["request_uri"] = c.Request.RequestURI + data["base_path"] = c.GetString("base_path") + c.HTML(http.StatusOK, name, getContext(data)) +} + +func getContext(h gin.H) gin.H { + a := gin.H{ + "cur_ver": config.GetVersion(), + } + if h != nil { + for key, value := range h { + a[key] = value + } + } + return a +} + +func isAjax(c *gin.Context) bool { + return c.GetHeader("X-Requested-With") == "XMLHttpRequest" +} diff --git a/web/controller/xui.go b/web/controller/xui.go new file mode 100644 index 00000000..5832be84 --- /dev/null +++ b/web/controller/xui.go @@ -0,0 +1,42 @@ +package controller + +import ( + "github.com/gin-gonic/gin" +) + +type XUIController struct { + BaseController + + inboundController *InboundController + settingController *SettingController +} + +func NewXUIController(g *gin.RouterGroup) *XUIController { + a := &XUIController{} + a.initRouter(g) + return a +} + +func (a *XUIController) initRouter(g *gin.RouterGroup) { + g = g.Group("/xui") + g.Use(a.checkLogin) + + g.GET("/", a.index) + g.GET("/inbounds", a.inbounds) + g.GET("/setting", a.setting) + + a.inboundController = NewInboundController(g) + a.settingController = NewSettingController(g) +} + +func (a *XUIController) index(c *gin.Context) { + html(c, "index.html", "pages.index.title", nil) +} + +func (a *XUIController) inbounds(c *gin.Context) { + html(c, "inbounds.html", "pages.inbounds.title", nil) +} + +func (a *XUIController) setting(c *gin.Context) { + html(c, "setting.html", "pages.setting.title", nil) +} -- cgit v1.2.3