nezha/cmd/dashboard/controller/controller.go

261 lines
7.9 KiB
Go
Raw Normal View History

2019-12-08 16:59:58 +08:00
package controller
import (
"errors"
2019-12-08 16:59:58 +08:00
"fmt"
2024-11-29 21:31:39 +08:00
"io"
"io/fs"
2023-11-29 09:42:51 +08:00
"log"
"net/http"
2024-10-24 21:33:36 +08:00
"os"
2024-11-30 00:02:45 +08:00
"path"
"strings"
2019-12-08 16:59:58 +08:00
2024-10-20 00:09:16 +08:00
jwt "github.com/appleboy/gin-jwt/v2"
2021-05-10 18:04:38 +08:00
"github.com/gin-contrib/pprof"
2019-12-08 16:59:58 +08:00
"github.com/gin-gonic/gin"
2024-10-19 23:14:53 +08:00
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
2019-12-08 16:59:58 +08:00
2024-11-28 19:38:54 +08:00
"github.com/nezhahq/nezha/cmd/dashboard/controller/waf"
docs "github.com/nezhahq/nezha/cmd/dashboard/docs"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/service/singleton"
2019-12-08 16:59:58 +08:00
)
2024-11-29 21:31:39 +08:00
func ServeWeb(adminFrontend, userFrontend fs.FS) http.Handler {
2019-12-08 23:18:29 +08:00
gin.SetMode(gin.ReleaseMode)
2021-05-10 18:04:38 +08:00
r := gin.Default()
2024-10-24 21:33:36 +08:00
2024-07-14 19:41:50 +08:00
if singleton.Conf.Debug {
gin.SetMode(gin.DebugMode)
pprof.Register(r)
}
if singleton.Conf.Debug {
2024-10-25 00:19:44 +08:00
log.Printf("NEZHA>> Swagger(%s) UI available at http://localhost:%d/swagger/index.html", docs.SwaggerInfo.Version, singleton.Conf.ListenPort)
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
}
2024-10-20 14:05:43 +08:00
2024-11-22 23:57:25 +08:00
r.Use(waf.RealIp)
r.Use(waf.Waf)
r.Use(recordPath)
2024-11-29 21:31:39 +08:00
routers(r, adminFrontend, userFrontend)
2021-08-10 20:13:17 +08:00
2024-10-22 23:44:50 +08:00
return r
2019-12-08 16:59:58 +08:00
}
2024-11-29 21:31:39 +08:00
func routers(r *gin.Engine, adminFrontend, userFrontend fs.FS) {
2024-10-20 00:09:16 +08:00
authMiddleware, err := jwt.New(initParams())
if err != nil {
log.Fatal("JWT Error:" + err.Error())
}
2024-10-20 23:23:04 +08:00
if err := authMiddleware.MiddlewareInit(); err != nil {
log.Fatal("authMiddleware.MiddlewareInit Error:" + err.Error())
}
2024-10-20 14:05:43 +08:00
api := r.Group("api/v1")
api.POST("/login", authMiddleware.LoginHandler)
2024-10-20 00:09:16 +08:00
optionalAuth := api.Group("", optionalAuthMiddleware(authMiddleware))
2024-10-21 23:00:51 +08:00
optionalAuth.GET("/ws/server", commonHandler(serverStream))
optionalAuth.GET("/server-group", commonHandler(listServerGroup))
2024-10-20 23:23:04 +08:00
optionalAuth.GET("/service", commonHandler(showService))
optionalAuth.GET("/service/:id", commonHandler(listServiceHistory))
optionalAuth.GET("/service/server", commonHandler(listServerWithServices))
2024-10-27 13:10:07 +08:00
optionalAuth.GET("/setting", commonHandler(listConfig))
2024-10-20 14:05:43 +08:00
auth := api.Group("", authMiddleware.MiddlewareFunc())
2024-10-22 22:01:01 +08:00
auth.GET("/refresh-token", authMiddleware.RefreshHandler)
2024-10-21 23:00:51 +08:00
2024-10-22 22:01:01 +08:00
auth.POST("/terminal", commonHandler(createTerminal))
auth.GET("/ws/terminal/:id", commonHandler(terminalStream))
2024-10-25 09:09:08 +08:00
auth.GET("/file", commonHandler(createFM))
auth.GET("/ws/file/:id", commonHandler(fmStream))
2024-11-03 23:28:10 +08:00
auth.GET("/profile", commonHandler(getProfile))
auth.POST("/profile", commonHandler(updateProfile))
auth.GET("/user", commonHandler(listUser))
auth.POST("/user", commonHandler(createUser))
auth.POST("/batch-delete/user", commonHandler(batchDeleteUser))
auth.GET("/service/list", commonHandler(listService))
2024-10-25 00:13:45 +08:00
auth.POST("/service", commonHandler(createService))
auth.PATCH("/service/:id", commonHandler(updateService))
auth.POST("/batch-delete/service", commonHandler(batchDeleteService))
2024-10-23 23:06:11 +08:00
auth.POST("/server-group", commonHandler(createServerGroup))
auth.PATCH("/server-group/:id", commonHandler(updateServerGroup))
2024-10-21 23:00:51 +08:00
auth.POST("/batch-delete/server-group", commonHandler(batchDeleteServerGroup))
auth.GET("/notification-group", commonHandler(listNotificationGroup))
auth.POST("/notification-group", commonHandler(createNotificationGroup))
auth.PATCH("/notification-group/:id", commonHandler(updateNotificationGroup))
auth.POST("/batch-delete/notification-group", commonHandler(batchDeleteNotificationGroup))
2024-10-23 17:34:15 +08:00
auth.GET("/server", commonHandler(listServer))
auth.PATCH("/server/:id", commonHandler(updateServer))
2024-10-21 23:00:51 +08:00
auth.POST("/batch-delete/server", commonHandler(batchDeleteServer))
2024-11-20 21:36:21 +08:00
auth.POST("/force-update/server", commonHandler(forceUpdateServer))
2024-10-21 23:00:51 +08:00
auth.GET("/notification", commonHandler(listNotification))
auth.POST("/notification", commonHandler(createNotification))
auth.PATCH("/notification/:id", commonHandler(updateNotification))
auth.POST("/batch-delete/notification", commonHandler(batchDeleteNotification))
2024-10-26 08:16:57 +08:00
auth.GET("/alert-rule", commonHandler(listAlertRule))
auth.POST("/alert-rule", commonHandler(createAlertRule))
auth.PATCH("/alert-rule/:id", commonHandler(updateAlertRule))
auth.POST("/batch-delete/alert-rule", commonHandler(batchDeleteAlertRule))
auth.GET("/cron", commonHandler(listCron))
auth.POST("/cron", commonHandler(createCron))
auth.PATCH("/cron/:id", commonHandler(updateCron))
auth.GET("/cron/:id/manual", commonHandler(manualTriggerCron))
auth.POST("/batch-delete/cron", commonHandler(batchDeleteCron))
2024-10-21 23:00:51 +08:00
auth.GET("/ddns", commonHandler(listDDNS))
auth.GET("/ddns/providers", commonHandler(listProviders))
auth.POST("/ddns", commonHandler(createDDNS))
auth.PATCH("/ddns/:id", commonHandler(updateDDNS))
2024-10-21 23:00:51 +08:00
auth.POST("/batch-delete/ddns", commonHandler(batchDeleteDDNS))
2024-10-24 21:33:36 +08:00
auth.GET("/nat", commonHandler(listNAT))
auth.POST("/nat", commonHandler(createNAT))
auth.PATCH("/nat/:id", commonHandler(updateNAT))
auth.POST("/batch-delete/nat", commonHandler(batchDeleteNAT))
auth.GET("/waf", commonHandler(listBlockedAddress))
auth.POST("/batch-delete/waf", commonHandler(batchDeleteBlockedAddress))
2024-10-27 13:10:07 +08:00
auth.PATCH("/setting", commonHandler(updateConfig))
2024-11-29 21:31:39 +08:00
r.NoRoute(fallbackToFrontend(adminFrontend, userFrontend))
2019-12-08 16:59:58 +08:00
}
func recordPath(c *gin.Context) {
url := c.Request.URL.String()
for _, p := range c.Params {
url = strings.Replace(url, p.Value, ":"+p.Key, 1)
}
c.Set("MatchedPath", url)
}
2024-10-21 23:00:51 +08:00
func newErrorResponse(err error) model.CommonResponse[any] {
return model.CommonResponse[any]{
Success: false,
Error: err.Error(),
}
}
2024-10-23 17:56:51 +08:00
type handlerFunc[T any] func(c *gin.Context) (T, error)
// There are many error types in gorm, so create a custom type to represent all
// gorm errors here instead
type gormError struct {
msg string
a []interface{}
}
func newGormError(format string, args ...interface{}) error {
return &gormError{
msg: format,
a: args,
}
}
func (ge *gormError) Error() string {
return fmt.Sprintf(ge.msg, ge.a...)
}
type wsError struct {
msg string
a []interface{}
}
func newWsError(format string, args ...interface{}) error {
return &wsError{
msg: format,
a: args,
}
}
func (we *wsError) Error() string {
return fmt.Sprintf(we.msg, we.a...)
}
2024-10-23 17:56:51 +08:00
func commonHandler[T any](handler handlerFunc[T]) func(*gin.Context) {
return func(c *gin.Context) {
2024-10-23 17:56:51 +08:00
data, err := handler(c)
if err == nil {
c.JSON(http.StatusOK, model.CommonResponse[T]{Success: true, Data: data})
return
}
switch err.(type) {
case *gormError:
2024-10-23 17:56:51 +08:00
log.Printf("NEZHA>> gorm error: %v", err)
2024-11-01 05:07:04 +08:00
c.JSON(http.StatusOK, newErrorResponse(singleton.Localizer.ErrorT("database error")))
2024-10-23 17:56:51 +08:00
return
case *wsError:
// Connection is upgraded to WebSocket, so c.Writer is no longer usable
if msg := err.Error(); msg != "" {
log.Printf("NEZHA>> websocket error: %v", err)
}
return
default:
2024-10-23 17:56:51 +08:00
c.JSON(http.StatusOK, newErrorResponse(err))
return
}
}
}
2024-10-24 21:33:36 +08:00
2024-11-29 21:31:39 +08:00
func fallbackToFrontend(adminFrontend, userFrontend fs.FS) func(*gin.Context) {
checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string) bool {
if _, err := os.Stat(path); err == nil {
c.File(path)
return true
}
f, err := fs.Open(path)
if err != nil {
return false
}
defer f.Close()
fileStat, err := f.Stat()
if err != nil {
return false
}
2024-11-29 22:49:17 +08:00
if fileStat.IsDir() {
return false
}
2024-11-29 21:31:39 +08:00
http.ServeContent(c.Writer, c.Request, path, fileStat.ModTime(), f.(io.ReadSeeker))
return true
2024-10-24 21:33:36 +08:00
}
2024-11-29 21:31:39 +08:00
return func(c *gin.Context) {
if strings.HasPrefix(c.Request.URL.Path, "/api") {
c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found")))
2024-10-24 21:33:36 +08:00
return
}
2024-11-29 21:31:39 +08:00
if strings.HasPrefix(c.Request.URL.Path, "/dashboard") {
stripPath := strings.TrimPrefix(c.Request.URL.Path, "/dashboard")
2024-11-30 00:02:45 +08:00
localFilePath := path.Join("admin-dist", stripPath)
2024-11-29 21:31:39 +08:00
if checkLocalFileOrFs(c, adminFrontend, localFilePath) {
return
}
if !checkLocalFileOrFs(c, adminFrontend, "admin-dist/index.html") {
c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found")))
}
return
}
2024-11-30 00:02:45 +08:00
localFilePath := path.Join("user-dist", c.Request.URL.Path)
2024-11-29 21:31:39 +08:00
if checkLocalFileOrFs(c, userFrontend, localFilePath) {
return
}
if !checkLocalFileOrFs(c, userFrontend, "user-dist/index.html") {
c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found")))
}
2024-10-24 21:33:36 +08:00
}
}