package api import ( "encoding/json" "s-ui/database" "s-ui/logger" "s-ui/service" "s-ui/util" "s-ui/util/common" "strconv" "strings" "time" "github.com/gin-gonic/gin" ) type APIHandler struct { service.SettingService service.UserService service.ConfigService service.ClientService service.TlsService service.InboundService service.OutboundService service.EndpointService service.PanelService service.StatsService service.ServerService } func NewAPIHandler(g *gin.RouterGroup) { a := &APIHandler{} a.initRouter(g) } func (a *APIHandler) initRouter(g *gin.RouterGroup) { g.Use(func(c *gin.Context) { path := c.Request.URL.Path if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") { checkLogin(c) } }) g.POST("/:postAction", a.postHandler) g.GET("/:getAction", a.getHandler) } func (a *APIHandler) postHandler(c *gin.Context) { var err error action := c.Param("postAction") remoteIP := getRemoteIp(c) loginUser := GetLoginUser(c) hostname := getHostname(c) switch action { case "login": loginUser, err := a.UserService.Login(c.Request.FormValue("user"), c.Request.FormValue("pass"), remoteIP) if err != nil { jsonMsg(c, "", err) return } sessionMaxAge, err := a.SettingService.GetSessionMaxAge() if err != nil { logger.Infof("Unable to get session's max age from DB") } err = SetLoginUser(c, loginUser, sessionMaxAge) if err == nil { logger.Info("user ", loginUser, " login success") } else { logger.Warning("login failed: ", err) } jsonMsg(c, "", nil) case "changePass": id := c.Request.FormValue("id") oldPass := c.Request.FormValue("oldPass") newUsername := c.Request.FormValue("newUsername") newPass := c.Request.FormValue("newPass") err = a.UserService.ChangePass(id, oldPass, newUsername, newPass) if err == nil { logger.Info("change user credentials success") jsonMsg(c, "save", nil) } else { logger.Warning("change user credentials failed:", err) jsonMsg(c, "", err) } case "save": obj := c.Request.FormValue("object") act := c.Request.FormValue("action") data := c.Request.FormValue("data") initUsers := c.Request.FormValue("initUsers") objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), initUsers, loginUser, hostname) if err != nil { jsonMsg(c, "save", err) return } err = a.loadPartialData(c, objs) if err != nil { jsonMsg(c, obj, err) } return case "restartApp": err = a.PanelService.RestartPanel(3) jsonMsg(c, "restartApp", err) case "restartSb": err = a.ConfigService.RestartCore() jsonMsg(c, "restartSb", err) case "linkConvert": link := c.Request.FormValue("link") result, _, err := util.GetOutbound(link, 0) jsonObj(c, result, err) case "importdb": file, _, err := c.Request.FormFile("db") if err != nil { jsonMsg(c, "", err) return } defer file.Close() err = database.ImportDB(file) jsonMsg(c, "", err) default: jsonMsg(c, "failed", common.NewError("unknown action: ", action)) } } func (a *APIHandler) getHandler(c *gin.Context) { action := c.Param("getAction") switch action { case "logout": loginUser := GetLoginUser(c) if loginUser != "" { logger.Infof("user %s logout", loginUser) } ClearSession(c) jsonMsg(c, "", nil) case "load": data, err := a.loadData(c) if err != nil { jsonMsg(c, "", err) return } jsonObj(c, data, nil) case "inbounds", "outbounds", "endpoints", "tls", "clients", "config": err := a.loadPartialData(c, []string{action}) if err != nil { jsonMsg(c, action, err) } return case "users": users, err := a.UserService.GetUsers() if err != nil { jsonMsg(c, "", err) return } jsonObj(c, *users, nil) case "settings": data, err := a.SettingService.GetAllSetting() if err != nil { jsonMsg(c, "", err) return } jsonObj(c, data, err) case "stats": resource := c.Query("resource") tag := c.Query("tag") limit, err := strconv.Atoi(c.Query("limit")) if err != nil { limit = 100 } data, err := a.StatsService.GetStats(resource, tag, limit) if err != nil { jsonMsg(c, "", err) return } jsonObj(c, data, err) case "status": request := c.Query("r") result := a.ServerService.GetStatus(request) jsonObj(c, result, nil) case "onlines": onlines, err := a.StatsService.GetOnlines() jsonObj(c, onlines, err) case "logs": count := c.Query("c") level := c.Query("l") logs := a.ServerService.GetLogs(count, level) jsonObj(c, logs, nil) case "changes": actor := c.Query("a") chngKey := c.Query("k") count := c.Query("c") changes := a.ConfigService.GetChanges(actor, chngKey, count) jsonObj(c, changes, nil) case "keypairs": kType := c.Query("k") options := c.Query("o") keypair := a.ServerService.GenKeypair(kType, options) jsonObj(c, keypair, nil) case "getdb": exclude := c.Query("exclude") db, err := database.GetDb(exclude) if err != nil { jsonMsg(c, "", err) return } c.Header("Content-Type", "application/octet-stream") c.Header("Content-Disposition", "attachment; filename=s-ui_"+time.Now().Format("20060102-150405")+".db") c.Writer.Write(db) default: jsonMsg(c, "failed", common.NewError("unknown action: ", action)) } } func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) { data := make(map[string]interface{}, 0) lu := c.Query("lu") isUpdated, err := a.ConfigService.CheckChanges(lu) if err != nil { return "", err } onlines, err := a.StatsService.GetOnlines() sysInfo := a.ServerService.GetSingboxInfo() if sysInfo["running"] == false { logs := a.ServerService.GetLogs("1", "debug") if len(logs) > 0 { data["lastLog"] = logs[0] } } if err != nil { return "", err } if isUpdated { config, err := a.SettingService.GetConfig() if err != nil { return "", err } clients, err := a.ClientService.GetAll() if err != nil { return "", err } tlsConfigs, err := a.TlsService.GetAll() if err != nil { return "", err } inbounds, err := a.InboundService.GetAll() if err != nil { return "", err } outbounds, err := a.OutboundService.GetAll() if err != nil { return "", err } endpoints, err := a.EndpointService.GetAll() if err != nil { return "", err } subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0]) if err != nil { return "", err } data["config"] = json.RawMessage(config) data["clients"] = clients data["tls"] = tlsConfigs data["inbounds"] = inbounds data["outbounds"] = outbounds data["endpoints"] = endpoints data["subURI"] = subURI data["onlines"] = onlines } else { data["onlines"] = onlines } return data, nil } func (a *APIHandler) loadPartialData(c *gin.Context, objs []string) error { data := make(map[string]interface{}, 0) id := c.Query("id") for _, obj := range objs { switch obj { case "inbounds": inbounds, err := a.InboundService.Get(id) if err != nil { return err } data[obj] = inbounds case "outbounds": outbounds, err := a.OutboundService.GetAll() if err != nil { return err } data[obj] = outbounds case "endpoints": endpoints, err := a.EndpointService.GetAll() if err != nil { return err } data[obj] = endpoints case "tls": tlsConfigs, err := a.TlsService.GetAll() if err != nil { return err } data[obj] = tlsConfigs case "clients": clients, err := a.ClientService.Get(id) if err != nil { return err } data[obj] = clients case "config": config, err := a.SettingService.GetConfig() if err != nil { return err } data[obj] = json.RawMessage(config) case "settings": settings, err := a.SettingService.GetAllSetting() if err != nil { return err } data[obj] = settings } } jsonObj(c, data, nil) return nil } func (a *APIHandler) postActions(c *gin.Context) (string, json.RawMessage, error) { var data map[string]json.RawMessage err := c.ShouldBind(&data) if err != nil { return "", nil, err } return string(data["action"]), data["data"], nil }