diff --git a/cmd/root.go b/cmd/root.go index 7b6fdf2..418d656 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,8 @@ import ( "fmt" "os" + "log/slog" + "github.com/gookit/event" "github.com/komari-monitor/komari/cmd/flags" "github.com/komari-monitor/komari/internal/eventType" @@ -43,8 +45,11 @@ Made by Akizon77 with love.`, } func Execute() { - event.Trigger(eventType.ProcessStart, nil) - defer event.Trigger(eventType.ProcessExit, nil) + err, _ := event.Trigger(eventType.ProcessStart, event.M{}) + if err != nil { + slog.Error("Something went wrong during process start.", slog.Any("error", err)) + os.Exit(1) + } if err := RootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/cmd/server.go b/cmd/server.go index 24e50d2..22ecda1 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -2,37 +2,24 @@ package cmd import ( "context" - "fmt" "log" + "log/slog" "net/http" "os" "os/signal" - "strings" "syscall" "time" "github.com/gin-gonic/gin" "github.com/gookit/event" "github.com/komari-monitor/komari/cmd/flags" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal" "github.com/komari-monitor/komari/internal/conf" - "github.com/komari-monitor/komari/internal/database" - "github.com/komari-monitor/komari/internal/database/accounts" "github.com/komari-monitor/komari/internal/database/auditlog" - "github.com/komari-monitor/komari/internal/database/dbcore" - "github.com/komari-monitor/komari/internal/database/models" - d_notification "github.com/komari-monitor/komari/internal/database/notification" "github.com/komari-monitor/komari/internal/database/records" "github.com/komari-monitor/komari/internal/database/tasks" "github.com/komari-monitor/komari/internal/eventType" - "github.com/komari-monitor/komari/internal/geoip" logutil "github.com/komari-monitor/komari/internal/log" - "github.com/komari-monitor/komari/internal/messageSender" - "github.com/komari-monitor/komari/internal/notifier" - "github.com/komari-monitor/komari/internal/oauth" - "github.com/komari-monitor/komari/internal/patch" - "github.com/komari-monitor/komari/internal/restore" - "github.com/komari-monitor/komari/pkg/cloudflared" "github.com/komari-monitor/komari/server" "github.com/spf13/cobra" ) @@ -59,63 +46,19 @@ func RunServer() { if err := os.MkdirAll("./data/theme", os.ModePerm); err != nil { log.Fatalf("Failed to create theme directory: %v", err) } - // 进行备份恢复 - if restore.NeedBackupRestore() { - restore.RestoreBackup() - } - conf.Load() - InitDatabase() - patch.ApplyPatch() - + internal.All() if conf.Version != conf.Version_Development { gin.SetMode(gin.ReleaseMode) } - config, err := conf.GetWithV1Format() - if err != nil { - log.Fatal(err) - } - r := gin.New() r.Use(logutil.GinLogger()) r.Use(logutil.GinRecovery()) - event.Trigger(eventType.ServerInitializeStart, event.M{"config": config, "engine": r}) - - go geoip.InitGeoIp() - go DoScheduledWork() - go messageSender.Initialize() - go oauth.Initialize() - - server.StartNezhaGRPCServer(config.NezhaCompatListen) - - event.On(eventType.ConfigUpdated, event.ListenerFunc(func(e event.Event) error { - newConf := e.Get("new").(conf.Config) - oldConf := e.Get("old").(conf.Config) - if newConf.Login.OAuthProvider != oldConf.Login.OAuthProvider { - oidcProvider, err := database.GetOidcConfigByName(newConf.Login.OAuthProvider) - if err != nil { - log.Printf("Failed to get OIDC provider config: %v", err) - } else { - log.Printf("Using %s as OIDC provider", oidcProvider.Name) - } - err = oauth.LoadProvider(oidcProvider.Name, oidcProvider.Addition) - if err != nil { - auditlog.EventLog("error", fmt.Sprintf("Failed to load OIDC provider: %v", err)) - } - } - if newConf.Notification.NotificationMethod != oldConf.Notification.NotificationMethod { - messageSender.Initialize() - } - return nil - }), event.Max) - - // 初始化 cloudflared - if strings.ToLower(GetEnv("KOMARI_ENABLE_CLOUDFLARED", "false")) == "true" { - err := cloudflared.RunCloudflared() // 阻塞,确保cloudflared跑起来 - if err != nil { - log.Fatalf("Failed to run cloudflared: %v", err) - } + err, _ := event.Trigger(eventType.ServerInitializeStart, event.M{"engine": r}) + if err != nil { + slog.Error("Something went wrong during ServerInitializeStart event.", slog.Any("error", err)) + os.Exit(1) } server.Init(r) @@ -125,17 +68,25 @@ func RunServer() { Handler: r, } - event.Trigger(eventType.ServerInitializeDone, event.M{"config": config}) + event.Trigger(eventType.ServerInitializeDone, event.M{}) + ScheduledEventTasksInit() - log.Printf("Starting server on %s ...", flags.Listen) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - OnFatal(err) - log.Fatalf("listen: %s\n", err) - } quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, syscall.SIGTERM) + + log.Printf("Starting server on %s ...", flags.Listen) + + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + OnFatal(err) + event.Trigger(eventType.ProcessExit, event.M{}) + log.Fatalf("listen: %s\n", err) + } + }() + <-quit OnShutdown() + event.Trigger(eventType.ProcessExit, event.M{}) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { @@ -144,66 +95,72 @@ func RunServer() { } -func InitDatabase() { - // // 打印数据库类型和连接信息 - // if flags.DatabaseType == "mysql" { - // log.Printf("使用 MySQL 数据库连接: %s@%s:%s/%s", - // flags.DatabaseUser, flags.DatabaseHost, flags.DatabasePort, flags.DatabaseName) - // log.Printf("环境变量配置: [KOMARI_DB_TYPE=%s] [KOMARI_DB_HOST=%s] [KOMARI_DB_PORT=%s] [KOMARI_DB_USER=%s] [KOMARI_DB_NAME=%s]", - // os.Getenv("KOMARI_DB_TYPE"), os.Getenv("KOMARI_DB_HOST"), os.Getenv("KOMARI_DB_PORT"), - // os.Getenv("KOMARI_DB_USER"), os.Getenv("KOMARI_DB_NAME")) - // } else { - // log.Printf("使用 SQLite 数据库文件: %s", flags.DatabaseFile) - // log.Printf("环境变量配置: [KOMARI_DB_TYPE=%s] [KOMARI_DB_FILE=%s]", - // os.Getenv("KOMARI_DB_TYPE"), os.Getenv("KOMARI_DB_FILE")) - // } - var count int64 = 0 - if dbcore.GetDBInstance().Model(&models.User{}).Count(&count); count == 0 { - user, passwd, err := accounts.CreateDefaultAdminAccount() - if err != nil { - panic(err) - } - log.Println("Default admin account created. Username:", user, ", Password:", passwd) - } -} - // #region 定时任务 func DoScheduledWork() { tasks.ReloadPingSchedule() - d_notification.ReloadLoadNotificationSchedule() - ticker := time.NewTicker(time.Minute * 30) - minute := time.NewTicker(60 * time.Second) - //records.DeleteRecordBefore(time.Now().Add(-time.Hour * 24 * 30)) - records.CompactRecord() - cfg, _ := conf.GetWithV1Format() - go notifier.CheckExpireScheduledWork() - for { - select { - case <-ticker.C: - records.DeleteRecordBefore(time.Now().Add(-time.Hour * time.Duration(cfg.RecordPreserveTime))) - records.CompactRecord() - tasks.ClearTaskResultsByTimeBefore(time.Now().Add(-time.Hour * time.Duration(cfg.RecordPreserveTime))) - tasks.DeletePingRecordsBefore(time.Now().Add(-time.Hour * time.Duration(cfg.PingRecordPreserveTime))) - auditlog.RemoveOldLogs() - case <-minute.C: - api.SaveClientReportToDB() - if !cfg.RecordEnabled { - records.DeleteAll() - tasks.DeleteAllPingRecords() - } - // 每分钟检查一次流量提醒 - go notifier.CheckTraffic() - } - } + //records.DeleteRecordBefore(time.Now().Add(-time.Hour * 24 * 30)) + + records.CompactRecord() + + event.On(eventType.SchedulerEvery30Minutes, event.ListenerFunc(func(e event.Event) error { + cfg, err := conf.GetWithV1Format() + if err != nil { + slog.Warn("Failed to get config in scheduled task:", "error", err) + return err + } + records.DeleteRecordBefore(time.Now().Add(-time.Hour * time.Duration(cfg.RecordPreserveTime))) + records.CompactRecord() + tasks.ClearTaskResultsByTimeBefore(time.Now().Add(-time.Hour * time.Duration(cfg.RecordPreserveTime))) + tasks.DeletePingRecordsBefore(time.Now().Add(-time.Hour * time.Duration(cfg.PingRecordPreserveTime))) + auditlog.RemoveOldLogs() + return nil + })) + + event.On(eventType.SchedulerEveryMinute, event.ListenerFunc(func(e event.Event) error { + cfg, err := conf.GetWithV1Format() + if err != nil { + slog.Warn("Failed to get config in scheduled task:", "error", err) + return err + } + if !cfg.RecordEnabled { + records.DeleteAll() + tasks.DeleteAllPingRecords() + } + + return nil + })) } func OnShutdown() { auditlog.Log("", "", "server is shutting down", "info") - cloudflared.Kill() } func OnFatal(err error) { auditlog.Log("", "", "server encountered a fatal error: "+err.Error(), "error") - cloudflared.Kill() +} + +func ScheduledEventTasksInit() { + go DoScheduledWork() + go func() { + every1m := time.NewTicker(1 * time.Minute) + every5m := time.NewTicker(5 * time.Minute) + every30m := time.NewTicker(30 * time.Minute) + every1h := time.NewTicker(1 * time.Hour) + every1d := time.NewTicker(24 * time.Hour) + for { + select { + case <-every1m.C: + event.Async(eventType.SchedulerEveryMinute, event.M{"interval": "1m"}) + case <-every5m.C: + event.Async(eventType.SchedulerEvery5Minutes, event.M{"interval": "5m"}) + case <-every30m.C: + event.Async(eventType.SchedulerEvery30Minutes, event.M{"interval": "30m"}) + case <-every1h.C: + event.Async(eventType.SchedulerEveryHour, event.M{"interval": "1h"}) + case <-every1d.C: + event.Async(eventType.SchedulerEveryDay, event.M{"interval": "1d"}) + } + } + }() } diff --git a/internal/all.go b/internal/all.go new file mode 100644 index 0000000..f953f81 --- /dev/null +++ b/internal/all.go @@ -0,0 +1,27 @@ +package internal + +import ( + // Import all internal packages to ensure their init() functions are executed + _ "github.com/komari-monitor/komari/internal/api_rpc" + _ "github.com/komari-monitor/komari/internal/api_v1" + _ "github.com/komari-monitor/komari/internal/client" + _ "github.com/komari-monitor/komari/internal/cloudflared" + _ "github.com/komari-monitor/komari/internal/common" + _ "github.com/komari-monitor/komari/internal/conf" + _ "github.com/komari-monitor/komari/internal/database" + _ "github.com/komari-monitor/komari/internal/eventType" + _ "github.com/komari-monitor/komari/internal/geoip" + _ "github.com/komari-monitor/komari/internal/log" + _ "github.com/komari-monitor/komari/internal/messageSender" + _ "github.com/komari-monitor/komari/internal/nezha" + _ "github.com/komari-monitor/komari/internal/notifier" + _ "github.com/komari-monitor/komari/internal/oauth" + _ "github.com/komari-monitor/komari/internal/patch" + _ "github.com/komari-monitor/komari/internal/pingSchedule" + _ "github.com/komari-monitor/komari/internal/plugin" + _ "github.com/komari-monitor/komari/internal/renewal" + _ "github.com/komari-monitor/komari/internal/restore" + _ "github.com/komari-monitor/komari/internal/ws" +) + +func All() {} diff --git a/internal/api_rpc/common.go b/internal/api_rpc/common.go index 1f0160b..ebdef15 100644 --- a/internal/api_rpc/common.go +++ b/internal/api_rpc/common.go @@ -8,7 +8,7 @@ import ( "strings" "time" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/vars" "github.com/komari-monitor/komari/internal/common" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database" @@ -489,7 +489,7 @@ func getNodeRecentStatus(ctx context.Context, req *rpc.JsonRpcRequest) (any, *rp } } - raw, _ := api.Records.Get(params.UUID) + raw, _ := vars.Records.Get(params.UUID) reports, _ := raw.([]common.Report) // 扁平化为 { count, records: [] } diff --git a/internal/api_rpc/init.go b/internal/api_rpc/init.go new file mode 100644 index 0000000..4b60f71 --- /dev/null +++ b/internal/api_rpc/init.go @@ -0,0 +1,15 @@ +package api_rpc + +import ( + "github.com/gin-gonic/gin" + "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + event.On(eventType.ServerInitializeStart, event.ListenerFunc(func(e event.Event) error { + r := e.Get("engine").(*gin.Engine) + RegisterRouters("/api/rpc2", r) + return nil + })) +} diff --git a/internal/api_v1/AdminAuthMiddleware.go b/internal/api_v1/AdminAuthMiddleware.go index 735d3ec..d8b8b92 100644 --- a/internal/api_v1/AdminAuthMiddleware.go +++ b/internal/api_v1/AdminAuthMiddleware.go @@ -3,6 +3,7 @@ package api_v1 import ( "net/http" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/accounts" "github.com/gin-gonic/gin" @@ -20,7 +21,7 @@ func AdminAuthMiddleware() gin.HandlerFunc { // session-based authentication session, err := c.Cookie("session_token") if err != nil { - RespondError(c, http.StatusUnauthorized, "Unauthorized.") + resp.RespondError(c, http.StatusUnauthorized, "Unauthorized.") c.Abort() return } @@ -28,7 +29,7 @@ func AdminAuthMiddleware() gin.HandlerFunc { // Komari is a single user system uuid, err := accounts.GetSession(session) if err != nil { - RespondError(c, http.StatusUnauthorized, "Unauthorized.") + resp.RespondError(c, http.StatusUnauthorized, "Unauthorized.") c.Abort() return } diff --git a/internal/api_v1/Common.go b/internal/api_v1/Common.go index 18e7941..603cc94 100644 --- a/internal/api_v1/Common.go +++ b/internal/api_v1/Common.go @@ -2,45 +2,27 @@ package api_v1 import ( "log" - "net/http" - "sync" "time" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" + "github.com/komari-monitor/komari/internal/api_v1/vars" "github.com/komari-monitor/komari/internal/common" + "github.com/komari-monitor/komari/internal/conf" "github.com/patrickmn/go-cache" "strconv" - "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" "github.com/komari-monitor/komari/pkg/utils" ) -var ( - Records = cache.New(1*time.Minute, 1*time.Minute) -) - -type TerminalSession struct { - UUID string - UserUUID string - Browser *websocket.Conn - Agent *websocket.Conn - RequesterIp string -} - -var TerminalSessionsMutex = &sync.Mutex{} -var TerminalSessions = make(map[string]*TerminalSession) - func SaveClientReportToDB() error { lastMinute := time.Now().Add(-time.Minute).Unix() var records []models.Record var gpuRecords []models.GPURecord // 遍历所有客户端记录 - for uuid, x := range Records.Items() { + for uuid, x := range vars.Records.Items() { if uuid == "" { continue } @@ -60,7 +42,7 @@ func SaveClientReportToDB() error { } // 更新缓存 - Records.Set(uuid, filtered, cache.DefaultExpiration) + vars.Records.Set(uuid, filtered, cache.DefaultExpiration) // 计算平均报告并添加到记录列表 if len(filtered) > 0 { @@ -113,38 +95,6 @@ func SaveClientReportToDB() error { return nil } -type Response struct { - Status string `json:"status"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` -} - -// Respond sends a standardized JSON response. -func Respond(c *gin.Context, httpStatus int, status string, message string, data interface{}) { - c.JSON(httpStatus, Response{Status: status, Message: message, Data: data}) -} - -// RespondSuccess sends a success response with data. -func RespondSuccess(c *gin.Context, data interface{}) { - Respond(c, http.StatusOK, "success", "", data) -} - -// RespondSuccessMessage sends a success response with message and data. -func RespondSuccessMessage(c *gin.Context, message string, data interface{}) { - Respond(c, http.StatusOK, "success", message, data) -} - -// RespondError sends an error response with message. -func RespondError(c *gin.Context, httpStatus int, message string) { - Respond(c, httpStatus, "error", message, nil) -} -func GetVersion(c *gin.Context) { - RespondSuccess(c, gin.H{ - "version": conf.Version, - "hash": conf.CommitHash, - }) -} - func isApiKeyValid(apiKey string) bool { cfg, err := conf.GetWithV1Format() if err != nil { diff --git a/internal/api_v1/PrivateSiteMiddleware.go b/internal/api_v1/PrivateSiteMiddleware.go index dc30f04..6ea1ac9 100644 --- a/internal/api_v1/PrivateSiteMiddleware.go +++ b/internal/api_v1/PrivateSiteMiddleware.go @@ -3,6 +3,7 @@ package api_v1 import ( "net/http" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database/accounts" @@ -48,7 +49,7 @@ func PrivateSiteMiddleware() gin.HandlerFunc { } conf, err := conf.GetWithV1Format() if err != nil { - RespondError(c, http.StatusInternalServerError, "Failed to get configuration.") + resp.RespondError(c, http.StatusInternalServerError, "Failed to get configuration.") c.Abort() return } @@ -60,13 +61,13 @@ func PrivateSiteMiddleware() gin.HandlerFunc { // 如果是私有站点,检查是否有 session session, err := c.Cookie("session_token") if err != nil { - RespondError(c, http.StatusUnauthorized, "Private site is enabled, please login first.") + resp.RespondError(c, http.StatusUnauthorized, "Private site is enabled, please login first.") c.Abort() return } _, err = accounts.GetSession(session) if err != nil { - RespondError(c, http.StatusUnauthorized, "Private site is enabled, please login first.") + resp.RespondError(c, http.StatusUnauthorized, "Private site is enabled, please login first.") c.Abort() return } diff --git a/internal/api_v1/admin/2fa.go b/internal/api_v1/admin/2fa.go index 01e0a77..62f95ff 100644 --- a/internal/api_v1/admin/2fa.go +++ b/internal/api_v1/admin/2fa.go @@ -4,7 +4,7 @@ import ( "image/png" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/accounts" "github.com/pquerna/otp/totp" ) @@ -12,7 +12,7 @@ import ( func Generate2FA(c *gin.Context) { secret, img, err := accounts.Generate2Fa() if err != nil { - api.RespondError(c, 500, "Failed to generate 2FA: "+err.Error()) + resp.RespondError(c, 500, "Failed to generate 2FA: "+err.Error()) return } c.SetCookie("2fa_secret", secret, 1800, "/", "", false, true) @@ -26,29 +26,29 @@ func Enable2FA(c *gin.Context) { secret, _ := c.Cookie("2fa_secret") code := c.Query("code") if secret == "" || uuid == nil || code == "" { - api.RespondError(c, 400, "2FA secret or code not provided") + resp.RespondError(c, 400, "2FA secret or code not provided") return } if !totp.Validate(code, secret) { - api.RespondError(c, 400, "Invalid 2FA code") + resp.RespondError(c, 400, "Invalid 2FA code") return } err := accounts.Enable2Fa(uuid.(string), secret) if err != nil { - api.RespondError(c, 500, "Failed to enable 2FA: "+err.Error()) + resp.RespondError(c, 500, "Failed to enable 2FA: "+err.Error()) return } c.SetCookie("2fa_secret", "", -1, "/", "", false, true) - api.RespondSuccess(c, "2FA enabled successfully") + resp.RespondSuccess(c, "2FA enabled successfully") } func Disable2FA(c *gin.Context) { uuid, _ := c.Get("uuid") err := accounts.Disable2Fa(uuid.(string)) if err != nil { - api.RespondError(c, 500, "Failed to disable 2FA: "+err.Error()) + resp.RespondError(c, 500, "Failed to disable 2FA: "+err.Error()) return } - api.RespondSuccess(c, "") + resp.RespondSuccess(c, "") } diff --git a/internal/api_v1/admin/client_weight.go b/internal/api_v1/admin/client_weight.go index b476820..71701de 100644 --- a/internal/api_v1/admin/client_weight.go +++ b/internal/api_v1/admin/client_weight.go @@ -2,7 +2,7 @@ package admin import ( "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/auditlog" "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" @@ -11,18 +11,18 @@ import ( func OrderWeight(c *gin.Context) { var req = make(map[string]int) if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, 400, "Invalid or missing request body: "+err.Error()) + resp.RespondError(c, 400, "Invalid or missing request body: "+err.Error()) return } db := dbcore.GetDBInstance() for uuid, weight := range req { err := db.Model(&models.Client{}).Where("uuid = ?", uuid).Update("weight", weight).Error if err != nil { - api.RespondError(c, 500, "Failed to update client weight: "+err.Error()) + resp.RespondError(c, 500, "Failed to update client weight: "+err.Error()) return } } uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "order clients", "info") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } diff --git a/internal/api_v1/admin/clipboard/clipboard.go b/internal/api_v1/admin/clipboard/clipboard.go index 4c515e9..d6956d0 100644 --- a/internal/api_v1/admin/clipboard/clipboard.go +++ b/internal/api_v1/admin/clipboard/clipboard.go @@ -5,7 +5,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/auditlog" clipboardDB "github.com/komari-monitor/komari/internal/database/clipboard" "github.com/komari-monitor/komari/internal/database/models" @@ -16,41 +16,41 @@ func GetClipboard(c *gin.Context) { idStr := c.Param("id") id, err := strconv.Atoi(idStr) if err != nil { - api.RespondError(c, http.StatusBadRequest, "Invalid ID") + resp.RespondError(c, http.StatusBadRequest, "Invalid ID") return } cb, err := clipboardDB.GetClipboardByID(id) if err != nil { - api.RespondError(c, http.StatusInternalServerError, "Failed to get clipboard: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to get clipboard: "+err.Error()) return } - api.RespondSuccess(c, cb) + resp.RespondSuccess(c, cb) } // ListClipboard lists all clipboard entries func ListClipboard(c *gin.Context) { list, err := clipboardDB.ListClipboard() if err != nil { - api.RespondError(c, http.StatusInternalServerError, "Failed to list clipboard: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to list clipboard: "+err.Error()) return } - api.RespondSuccess(c, list) + resp.RespondSuccess(c, list) } // CreateClipboard creates a new clipboard entry func CreateClipboard(c *gin.Context) { var req models.Clipboard if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) return } if err := clipboardDB.CreateClipboard(&req); err != nil { - api.RespondError(c, http.StatusInternalServerError, "Failed to create clipboard: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to create clipboard: "+err.Error()) return } userUUID, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), userUUID.(string), "create clipboard:"+strconv.Itoa(req.Id), "info") - api.RespondSuccess(c, req) + resp.RespondSuccess(c, req) } // UpdateClipboard updates an existing clipboard entry @@ -58,21 +58,21 @@ func UpdateClipboard(c *gin.Context) { idStr := c.Param("id") id, err := strconv.Atoi(idStr) if err != nil { - api.RespondError(c, http.StatusBadRequest, "Invalid ID") + resp.RespondError(c, http.StatusBadRequest, "Invalid ID") return } var fields map[string]interface{} if err := c.ShouldBindJSON(&fields); err != nil { - api.RespondError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) return } if err := clipboardDB.UpdateClipboardFields(id, fields); err != nil { - api.RespondError(c, http.StatusInternalServerError, "Failed to update clipboard: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to update clipboard: "+err.Error()) return } userUUID, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), userUUID.(string), "update clipboard:"+strconv.Itoa(id), "info") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } // DeleteClipboard deletes a clipboard entry @@ -80,16 +80,16 @@ func DeleteClipboard(c *gin.Context) { idStr := c.Param("id") id, err := strconv.Atoi(idStr) if err != nil { - api.RespondError(c, http.StatusBadRequest, "Invalid ID") + resp.RespondError(c, http.StatusBadRequest, "Invalid ID") return } if err := clipboardDB.DeleteClipboard(id); err != nil { - api.RespondError(c, http.StatusInternalServerError, "Failed to delete clipboard: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to delete clipboard: "+err.Error()) return } userUUID, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), userUUID.(string), "delete clipboard:"+strconv.Itoa(id), "warn") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } // BatchDeleteClipboard deletes multiple clipboard entries @@ -98,18 +98,18 @@ func BatchDeleteClipboard(c *gin.Context) { IDs []int `json:"ids" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) return } if len(req.IDs) == 0 { - api.RespondError(c, http.StatusBadRequest, "IDs cannot be empty") + resp.RespondError(c, http.StatusBadRequest, "IDs cannot be empty") return } if err := clipboardDB.DeleteClipboardBatch(req.IDs); err != nil { - api.RespondError(c, http.StatusInternalServerError, "Failed to batch delete clipboard: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to batch delete clipboard: "+err.Error()) return } userUUID, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), userUUID.(string), "batch delete clipboard: "+strconv.Itoa(len(req.IDs))+" items", "warn") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } diff --git a/internal/api_v1/admin/download.go b/internal/api_v1/admin/download.go index 32607eb..ec90f91 100644 --- a/internal/api_v1/admin/download.go +++ b/internal/api_v1/admin/download.go @@ -12,7 +12,7 @@ import ( "github.com/gin-gonic/gin" "github.com/komari-monitor/komari/cmd/flags" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/dbcore" ) @@ -108,14 +108,14 @@ func DownloadBackup(c *gin.Context) { // 1) 创建临时目录 tempDir, err := os.MkdirTemp("", "komari-backup-*") if err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating temporary directory: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating temporary directory: %v", err)) return } defer os.RemoveAll(tempDir) // 2) 复制 ./data 下除 .db/.db-wal/.db-shm 外的所有文件到临时目录 if err := copyDataToTempExcludingDB(tempDir); err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error copying data to temp: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error copying data to temp: %v", err)) return } @@ -125,18 +125,18 @@ func DownloadBackup(c *gin.Context) { if flags.DatabaseType == "sqlite" || flags.DatabaseType == "" { if err := backupSQLiteTo(destDB); err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error backing up sqlite database: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error backing up sqlite database: %v", err)) return } } else if dbFilePath != "" { // 非 sqlite 的情况:若配置了文件路径且存在,则直接复制(按用户需求仍然将名称固定为 komari.db) if _, err := os.Stat(dbFilePath); err == nil { if err := copyFile(dbFilePath, destDB); err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error copying database file: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error copying database file: %v", err)) return } } else if !os.IsNotExist(err) { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error stating database file: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error stating database file: %v", err)) return } } @@ -188,7 +188,7 @@ func DownloadBackup(c *gin.Context) { return err }) if err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error archiving temp folder: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error archiving temp folder: %v", err)) return } @@ -200,11 +200,11 @@ func DownloadBackup(c *gin.Context) { Modified: time.Now(), }) if err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating backup markup file: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating backup markup file: %v", err)) return } if _, err = markupWriter.Write([]byte(markupContent)); err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error writing backup markup file: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error writing backup markup file: %v", err)) return } } diff --git a/internal/api_v1/admin/exec.go b/internal/api_v1/admin/exec.go index 3e236a8..5c9b0d7 100644 --- a/internal/api_v1/admin/exec.go +++ b/internal/api_v1/admin/exec.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/auditlog" "github.com/komari-monitor/komari/internal/database/models" "github.com/komari-monitor/komari/internal/database/tasks" @@ -25,7 +25,7 @@ func Exec(c *gin.Context) { var onlineClients []string var offlineClients []string if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, 400, "Invalid or missing request body: "+err.Error()) + resp.RespondError(c, 400, "Invalid or missing request body: "+err.Error()) return } // for uuid := range ws.GetConnectedClients() { @@ -33,7 +33,7 @@ func Exec(c *gin.Context) { // onlineClients = append(onlineClients, uuid) // } // // else { - // // api.RespondError(c, 400, "Client not connected: "+uuid) + // // resp.RespondError(c, 400, "Client not connected: "+uuid) // // return // // } // } @@ -45,12 +45,12 @@ func Exec(c *gin.Context) { } } if len(onlineClients) == 0 { - api.RespondError(c, 400, "No clients connected") + resp.RespondError(c, 400, "No clients connected") return } taskId := utils.GenerateRandomString(16) if err := tasks.CreateTask(taskId, append(onlineClients, offlineClients...), req.Command); err != nil { - api.RespondError(c, 500, "Failed to create task: "+err.Error()) + resp.RespondError(c, 500, "Failed to create task: "+err.Error()) return } for _, uuid := range onlineClients { @@ -67,17 +67,17 @@ func Exec(c *gin.Context) { client := ws.GetConnectedClients()[uuid] if client != nil { if err := client.WriteMessage(websocket.TextMessage, payload); err != nil { - api.RespondError(c, 400, "Client connection is broke: "+uuid) + resp.RespondError(c, 400, "Client connection is broke: "+uuid) return } } else { - api.RespondError(c, 400, "Client connection is null: "+uuid) + resp.RespondError(c, 400, "Client connection is null: "+uuid) return } } uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "REC, task id: "+taskId, "warn") - api.RespondSuccess(c, gin.H{ + resp.RespondSuccess(c, gin.H{ "task_id": taskId, "clients": onlineClients, }) diff --git a/internal/api_v1/admin/log/log.go b/internal/api_v1/admin/log/log.go index 09f29a0..8cf7d73 100644 --- a/internal/api_v1/admin/log/log.go +++ b/internal/api_v1/admin/log/log.go @@ -4,7 +4,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" ) @@ -22,12 +22,12 @@ func GetLogs(c *gin.Context) { // If conversion fails, return an error limitInt, err := strconv.Atoi(limit) if err != nil || limitInt <= 0 { - api.RespondError(c, 400, "Invalid limit: "+limit) + resp.RespondError(c, 400, "Invalid limit: "+limit) return } pageInt, err := strconv.Atoi(page) if err != nil || pageInt <= 0 { - api.RespondError(c, 400, "Invalid page: "+page) + resp.RespondError(c, 400, "Invalid page: "+page) return } db := dbcore.GetDBInstance() @@ -37,13 +37,13 @@ func GetLogs(c *gin.Context) { var total int64 if err := db.Model(&models.Log{}).Count(&total).Error; err != nil { - api.RespondError(c, 500, "Failed to count logs: "+err.Error()) + resp.RespondError(c, 500, "Failed to count logs: "+err.Error()) return } if err := db.Order("time desc").Limit(limitInt).Offset(offset).Find(&logs).Error; err != nil { - api.RespondError(c, 500, "Failed to retrieve logs: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve logs: "+err.Error()) return } - api.RespondSuccess(c, gin.H{"logs": logs, "total": total}) + resp.RespondSuccess(c, gin.H{"logs": logs, "total": total}) } diff --git a/internal/api_v1/admin/messageSender.go b/internal/api_v1/admin/messageSender.go index 128b586..3d2f92a 100644 --- a/internal/api_v1/admin/messageSender.go +++ b/internal/api_v1/admin/messageSender.go @@ -2,7 +2,7 @@ package admin import ( "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database" "github.com/komari-monitor/komari/internal/database/models" @@ -16,38 +16,38 @@ func GetMessageSenderProvider(c *gin.Context) { // 如果指定了provider,返回单个提供者的配置 config, err := database.GetMessageSenderConfigByName(provider) if err != nil { - api.RespondError(c, 404, "Provider not found: "+err.Error()) + resp.RespondError(c, 404, "Provider not found: "+err.Error()) return } - api.RespondSuccess(c, config) + resp.RespondSuccess(c, config) return } // 否则返回所有提供者的配置项模板 providers := factory.GetSenderConfigs() if len(providers) == 0 { - api.RespondError(c, 404, "No message sender providers found") + resp.RespondError(c, 404, "No message sender providers found") return } - api.RespondSuccess(c, providers) + resp.RespondSuccess(c, providers) } func SetMessageSenderProvider(c *gin.Context) { var senderConfig models.MessageSenderProvider if err := c.ShouldBindJSON(&senderConfig); err != nil { - api.RespondError(c, 400, "Invalid configuration: "+err.Error()) + resp.RespondError(c, 400, "Invalid configuration: "+err.Error()) return } if senderConfig.Name == "" { - api.RespondError(c, 400, "Provider name is required") + resp.RespondError(c, 400, "Provider name is required") return } _, exists := factory.GetConstructor(senderConfig.Name) if !exists { - api.RespondError(c, 404, "Provider not found: "+senderConfig.Name) + resp.RespondError(c, 404, "Provider not found: "+senderConfig.Name) return } if err := database.SaveMessageSenderConfig(&senderConfig); err != nil { - api.RespondError(c, 500, "Failed to save message sender provider configuration: "+err.Error()) + resp.RespondError(c, 500, "Failed to save message sender provider configuration: "+err.Error()) return } cfg, _ := conf.GetWithV1Format() @@ -55,9 +55,9 @@ func SetMessageSenderProvider(c *gin.Context) { if cfg.NotificationMethod == senderConfig.Name { err := messageSender.LoadProvider(senderConfig.Name, senderConfig.Addition) if err != nil { - api.RespondError(c, 500, "Failed to load message sender provider: "+err.Error()) + resp.RespondError(c, 500, "Failed to load message sender provider: "+err.Error()) return } } - api.RespondSuccess(c, gin.H{"message": "Message sender provider set successfully"}) + resp.RespondSuccess(c, gin.H{"message": "Message sender provider set successfully"}) } diff --git a/internal/api_v1/admin/notification/load.go b/internal/api_v1/admin/notification/load.go index 6b0ee9a..0929b29 100644 --- a/internal/api_v1/admin/notification/load.go +++ b/internal/api_v1/admin/notification/load.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/models" "github.com/komari-monitor/komari/internal/database/notification" ) @@ -21,23 +21,23 @@ func AddLoadNotification(c *gin.Context) { } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, err.Error()) + resp.RespondError(c, http.StatusBadRequest, err.Error()) return } if req.Interval > 4*60 || req.Interval <= 0 { - api.RespondError(c, http.StatusBadRequest, "Interval must be between 1 and 240 minutes") + resp.RespondError(c, http.StatusBadRequest, "Interval must be between 1 and 240 minutes") return } if req.Ratio <= 0 || req.Ratio > 1 { - api.RespondError(c, http.StatusBadRequest, "Ratio must be between 0 and 1") + resp.RespondError(c, http.StatusBadRequest, "Ratio must be between 0 and 1") return } if taskID, err := notification.AddLoadNotification(req.Clients, req.Name, req.Metric, req.Threshold, req.Ratio, req.Interval); err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) } else { - api.RespondSuccess(c, gin.H{"task_id": taskID}) + resp.RespondSuccess(c, gin.H{"task_id": taskID}) } } @@ -47,14 +47,14 @@ func DeleteLoadNotification(c *gin.Context) { ID []uint `json:"id" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, err.Error()) + resp.RespondError(c, http.StatusBadRequest, err.Error()) return } if err := notification.DeleteLoadNotification(req.ID); err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) } else { - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } } @@ -65,26 +65,26 @@ func EditLoadNotification(c *gin.Context) { } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, "Invalid request data") + resp.RespondError(c, http.StatusBadRequest, "Invalid request data") return } if err := notification.EditLoadNotification(req.Notifications); err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) } else { // for _, notification := range req.Notifications { // notification.DeleteLoadNotification([]uint{notification.Id}) // } - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } } func GetAllLoadNotifications(c *gin.Context) { notifications, err := notification.GetAllLoadNotifications() if err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) return } - api.RespondSuccess(c, notifications) + resp.RespondSuccess(c, notifications) } diff --git a/internal/api_v1/admin/notification/offline.go b/internal/api_v1/admin/notification/offline.go index d5a7e35..40c48fc 100644 --- a/internal/api_v1/admin/notification/offline.go +++ b/internal/api_v1/admin/notification/offline.go @@ -2,7 +2,7 @@ package notification import ( "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + resp "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" "gorm.io/gorm/clause" @@ -12,7 +12,7 @@ import ( func EnableOfflineNotification(c *gin.Context) { var uuids []string if err := c.ShouldBindJSON(&uuids); err != nil { - api.RespondError(c, 400, "Invalid request body: "+err.Error()) + resp.RespondError(c, 400, "Invalid request body: "+err.Error()) return } var notifications []models.OfflineNotification @@ -30,17 +30,17 @@ func EnableOfflineNotification(c *gin.Context) { Select("client", "enable"). Create(notifications).Error if err != nil { - api.RespondError(c, 500, "Failed to enable offline notifications: "+err.Error()) + resp.RespondError(c, 500, "Failed to enable offline notifications: "+err.Error()) return } - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } // POST body : []uuid func DisableOfflineNotification(c *gin.Context) { var uuids []string if err := c.ShouldBindJSON(&uuids); err != nil { - api.RespondError(c, 400, "Invalid request body: "+err.Error()) + resp.RespondError(c, 400, "Invalid request body: "+err.Error()) return } var notifications []models.OfflineNotification @@ -58,29 +58,29 @@ func DisableOfflineNotification(c *gin.Context) { Select("client", "enable"). Create(notifications).Error if err != nil { - api.RespondError(c, 500, "Failed to disable offline notifications: "+err.Error()) + resp.RespondError(c, 500, "Failed to disable offline notifications: "+err.Error()) return } - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } func EditOfflineNotification(c *gin.Context) { var notifications []models.OfflineNotification if err := c.ShouldBindJSON(¬ifications); err != nil { - api.RespondError(c, 400, "Invalid request body: "+err.Error()) + resp.RespondError(c, 400, "Invalid request body: "+err.Error()) return } if len(notifications) == 0 { - api.RespondError(c, 400, "At least one notification is required") + resp.RespondError(c, 400, "At least one notification is required") return } for _, noti := range notifications { if noti.Client == "" { - api.RespondError(c, 400, "Client UUID cannot be empty") + resp.RespondError(c, 400, "Client UUID cannot be empty") return } if noti.GracePeriod <= 0 { - api.RespondError(c, 400, "GracePeriod must be a positive integer") + resp.RespondError(c, 400, "GracePeriod must be a positive integer") return } } @@ -92,18 +92,18 @@ func EditOfflineNotification(c *gin.Context) { Select("*"). Create(notifications).Error if err != nil { - api.RespondError(c, 500, "Failed to edit offline notifications: "+err.Error()) + resp.RespondError(c, 500, "Failed to edit offline notifications: "+err.Error()) return } - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } func ListOfflineNotifications(c *gin.Context) { var notifications []models.OfflineNotification err := dbcore.GetDBInstance().Model(&models.OfflineNotification{}).Find(¬ifications).Error if err != nil { - api.RespondError(c, 500, "Failed to list offline notifications: "+err.Error()) + resp.RespondError(c, 500, "Failed to list offline notifications: "+err.Error()) return } - api.RespondSuccess(c, notifications) + resp.RespondSuccess(c, notifications) } diff --git a/internal/api_v1/admin/oauth.go b/internal/api_v1/admin/oauth.go index 96de2fb..d53386f 100644 --- a/internal/api_v1/admin/oauth.go +++ b/internal/api_v1/admin/oauth.go @@ -2,7 +2,7 @@ package admin import ( "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database" "github.com/komari-monitor/komari/internal/database/accounts" @@ -15,7 +15,7 @@ func BindingExternalAccount(c *gin.Context) { session, _ := c.Cookie("session_token") user, err := accounts.GetUserBySession(session) if err != nil { - api.RespondError(c, 500, "No user found: "+err.Error()) + resp.RespondError(c, 500, "No user found: "+err.Error()) return } @@ -26,17 +26,17 @@ func UnbindExternalAccount(c *gin.Context) { session, _ := c.Cookie("session_token") user, err := accounts.GetUserBySession(session) if err != nil { - api.RespondError(c, 500, "No user found: "+err.Error()) + resp.RespondError(c, 500, "No user found: "+err.Error()) return } err = accounts.UnbindExternalAccount(user.UUID) if err != nil { - api.RespondError(c, 500, "Failed to unbind external account: "+err.Error()) + resp.RespondError(c, 500, "Failed to unbind external account: "+err.Error()) return } - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } func GetOidcProvider(c *gin.Context) { @@ -45,39 +45,39 @@ func GetOidcProvider(c *gin.Context) { // 如果指定了provider,返回单个提供者的配置 config, err := database.GetOidcConfigByName(provider) if err != nil { - api.RespondError(c, 404, "Provider not found: "+err.Error()) + resp.RespondError(c, 404, "Provider not found: "+err.Error()) return } - api.RespondSuccess(c, config) + resp.RespondSuccess(c, config) return } // 否则返回所有提供者的配置 providers := factory.GetProviderConfigs() if len(providers) == 0 { - api.RespondError(c, 404, "No OIDC providers found") + resp.RespondError(c, 404, "No OIDC providers found") return } - api.RespondSuccess(c, providers) + resp.RespondSuccess(c, providers) } func SetOidcProvider(c *gin.Context) { var oidcConfig models.OidcProvider if err := c.ShouldBindJSON(&oidcConfig); err != nil { - api.RespondError(c, 400, "Invalid configuration: "+err.Error()) + resp.RespondError(c, 400, "Invalid configuration: "+err.Error()) return } if oidcConfig.Name == "" { - api.RespondError(c, 400, "Provider name is required") + resp.RespondError(c, 400, "Provider name is required") return } _, exists := factory.GetConstructor(oidcConfig.Name) if !exists { - api.RespondError(c, 404, "Provider not found: "+oidcConfig.Name) + resp.RespondError(c, 404, "Provider not found: "+oidcConfig.Name) return } if err := database.SaveOidcConfig(&oidcConfig); err != nil { - api.RespondError(c, 500, "Failed to save OIDC provider configuration: "+err.Error()) + resp.RespondError(c, 500, "Failed to save OIDC provider configuration: "+err.Error()) return } cfg, _ := conf.GetWithV1Format() @@ -85,9 +85,9 @@ func SetOidcProvider(c *gin.Context) { if cfg.OAuthProvider == oidcConfig.Name { err := oauth.LoadProvider(oidcConfig.Name, oidcConfig.Addition) if err != nil { - api.RespondError(c, 500, "Failed to load OIDC provider: "+err.Error()) + resp.RespondError(c, 500, "Failed to load OIDC provider: "+err.Error()) return } } - api.RespondSuccess(c, gin.H{"message": "OIDC provider set successfully"}) + resp.RespondSuccess(c, gin.H{"message": "OIDC provider set successfully"}) } diff --git a/internal/api_v1/admin/ping.go b/internal/api_v1/admin/ping.go index 8fcb550..45bb342 100644 --- a/internal/api_v1/admin/ping.go +++ b/internal/api_v1/admin/ping.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/models" "github.com/komari-monitor/komari/internal/database/tasks" ) @@ -20,14 +20,14 @@ func AddPingTask(c *gin.Context) { } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, err.Error()) + resp.RespondError(c, http.StatusBadRequest, err.Error()) return } if taskID, err := tasks.AddPingTask(req.Clients, req.Name, req.Target, req.TaskType, req.Interval); err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) } else { - api.RespondSuccess(c, gin.H{"task_id": taskID}) + resp.RespondSuccess(c, gin.H{"task_id": taskID}) } } @@ -37,14 +37,14 @@ func DeletePingTask(c *gin.Context) { ID []uint `json:"id" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, err.Error()) + resp.RespondError(c, http.StatusBadRequest, err.Error()) return } if err := tasks.DeletePingTask(req.ID); err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) } else { - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } } @@ -55,26 +55,26 @@ func EditPingTask(c *gin.Context) { } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, "Invalid request data") + resp.RespondError(c, http.StatusBadRequest, "Invalid request data") return } if err := tasks.EditPingTask(req.Tasks); err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) } else { // for _, task := range req.Tasks { // tasks.DeletePingRecords([]uint{task.Id}) // } - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } } func GetAllPingTasks(c *gin.Context) { tasks, err := tasks.GetAllPingTasks() if err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) return } - api.RespondSuccess(c, tasks) + resp.RespondSuccess(c, tasks) } diff --git a/internal/api_v1/admin/sessions.go b/internal/api_v1/admin/sessions.go index 21981c0..fb09059 100644 --- a/internal/api_v1/admin/sessions.go +++ b/internal/api_v1/admin/sessions.go @@ -1,7 +1,7 @@ package admin import ( - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/accounts" "github.com/komari-monitor/komari/internal/database/auditlog" @@ -12,7 +12,7 @@ func GetSessions(c *gin.Context) { ss, err := accounts.GetAllSessions() if err != nil { - api.RespondError(c, 500, "Failed to retrieve sessions: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve sessions: "+err.Error()) return } current, _ := c.Cookie("session_token") @@ -24,27 +24,27 @@ func DeleteSession(c *gin.Context) { Session string `json:"session" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, 400, "Invalid request: "+err.Error()) + resp.RespondError(c, 400, "Invalid request: "+err.Error()) return } err := accounts.DeleteSession(req.Session) if err != nil { - api.RespondError(c, 500, "Failed to delete session: "+err.Error()) + resp.RespondError(c, 500, "Failed to delete session: "+err.Error()) return } uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "delete session", "info") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } func DeleteAllSession(c *gin.Context) { err := accounts.DeleteAllSessions() if err != nil { - api.RespondError(c, 500, "Failed to delete all sessions: "+err.Error()) + resp.RespondError(c, 500, "Failed to delete all sessions: "+err.Error()) return } uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "delete all sessions", "warn") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } diff --git a/internal/api_v1/admin/settings.go b/internal/api_v1/admin/settings.go index 862f56e..6f58c3e 100644 --- a/internal/api_v1/admin/settings.go +++ b/internal/api_v1/admin/settings.go @@ -3,7 +3,7 @@ package admin import ( "database/sql" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database/auditlog" "github.com/komari-monitor/komari/internal/database/records" @@ -21,7 +21,7 @@ func GetSettings(c *gin.Context) { cst = conf.V1Struct{Sitename: "Komari"} cst.ID = 1 conf.Save(cst) - api.RespondSuccess(c, cst) + resp.RespondSuccess(c, cst) return } c.JSON(500, gin.H{ @@ -29,20 +29,20 @@ func GetSettings(c *gin.Context) { "message": "Internal Server Error: " + err.Error(), }) } - api.RespondSuccess(c, cst) + resp.RespondSuccess(c, cst) } // EditSettings 更新自定义配置 func EditSettings(c *gin.Context) { cfg := make(map[string]interface{}) if err := c.ShouldBindJSON(&cfg); err != nil { - api.RespondError(c, 400, "Invalid or missing request body: "+err.Error()) + resp.RespondError(c, 400, "Invalid or missing request body: "+err.Error()) return } cfg["id"] = 1 // Only one record if err := conf.Update(cfg); err != nil { - api.RespondError(c, 500, "Failed to update settings: "+err.Error()) + resp.RespondError(c, 500, "Failed to update settings: "+err.Error()) return } @@ -59,7 +59,7 @@ func EditSettings(c *gin.Context) { message = message[:len(message)-2] } auditlog.Log(c.ClientIP(), uuid.(string), message, "info") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } func contains(slice []string, item string) bool { @@ -76,5 +76,5 @@ func ClearAllRecords(c *gin.Context) { tasks.DeleteAllPingRecords() uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "clear all records", "info") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } diff --git a/internal/api_v1/admin/task.go b/internal/api_v1/admin/task.go index 5916f45..3653c5f 100644 --- a/internal/api_v1/admin/task.go +++ b/internal/api_v1/admin/task.go @@ -2,21 +2,21 @@ package admin import ( "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/tasks" ) func GetTasks(c *gin.Context) { dbTasks, err := tasks.GetAllTasks() if err != nil { - api.RespondError(c, 500, "Failed to retrieve tasks: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve tasks: "+err.Error()) return } var responseTasks []gin.H for _, t := range dbTasks { results, err := tasks.GetTaskResultsByTaskId(t.TaskId) if err != nil { - api.RespondError(c, 500, "Failed to retrieve task results: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve task results: "+err.Error()) return } @@ -38,27 +38,27 @@ func GetTasks(c *gin.Context) { "results": filteredResults, }) } - api.RespondSuccess(c, responseTasks) + resp.RespondSuccess(c, responseTasks) } func GetTaskById(c *gin.Context) { taskId := c.Param("task_id") if taskId == "" { - api.RespondError(c, 400, "Task ID is required") + resp.RespondError(c, 400, "Task ID is required") return } task, err := tasks.GetTaskByTaskId(taskId) if err != nil { - api.RespondError(c, 500, "Failed to retrieve task: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve task: "+err.Error()) return } if task == nil { - api.RespondError(c, 404, "Task not found") + resp.RespondError(c, 404, "Task not found") return } results, err := tasks.GetTaskResultsByTaskId(taskId) if err != nil { - api.RespondError(c, 500, "Failed to retrieve task results: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve task results: "+err.Error()) return } var filteredResults []gin.H @@ -71,7 +71,7 @@ func GetTaskById(c *gin.Context) { "created_at": r.CreatedAt, }) } - api.RespondSuccess(c, gin.H{ + resp.RespondSuccess(c, gin.H{ "task_id": task.TaskId, "clients": task.Clients, "command": task.Command, @@ -82,73 +82,73 @@ func GetTaskById(c *gin.Context) { func GetTasksByClientId(c *gin.Context) { clientId := c.Param("uuid") if clientId == "" { - api.RespondError(c, 400, "Client ID is required") + resp.RespondError(c, 400, "Client ID is required") return } tasks, err := tasks.GetTasksByClientId(clientId) if err != nil { - api.RespondError(c, 500, "Failed to retrieve tasks: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve tasks: "+err.Error()) return } if len(tasks) == 0 { - api.RespondError(c, 404, "No tasks found for this client") + resp.RespondError(c, 404, "No tasks found for this client") return } - api.RespondSuccess(c, tasks) + resp.RespondSuccess(c, tasks) } func GetSpecificTaskResult(c *gin.Context) { taskId := c.Param("task_id") clientId := c.Param("uuid") if taskId == "" || clientId == "" { - api.RespondError(c, 400, "Task ID and Client ID are required") + resp.RespondError(c, 400, "Task ID and Client ID are required") return } result, err := tasks.GetSpecificTaskResult(taskId, clientId) if err != nil { - api.RespondError(c, 500, "Failed to retrieve task result: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve task result: "+err.Error()) return } if result == nil { - api.RespondError(c, 404, "No result found for this task and client") + resp.RespondError(c, 404, "No result found for this task and client") return } - api.RespondSuccess(c, result) + resp.RespondSuccess(c, result) } // Param: task_id func GetTaskResultsByTaskId(c *gin.Context) { taskId := c.Param("task_id") if taskId == "" { - api.RespondError(c, 400, "Task ID is required") + resp.RespondError(c, 400, "Task ID is required") return } results, err := tasks.GetTaskResultsByTaskId(taskId) if err != nil { - api.RespondError(c, 500, "Failed to retrieve task results: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve task results: "+err.Error()) return } if len(results) == 0 { - api.RespondError(c, 404, "No results found for this task") + resp.RespondError(c, 404, "No results found for this task") return } - api.RespondSuccess(c, results) + resp.RespondSuccess(c, results) } func GetAllTaskResultByUUID(c *gin.Context) { clientId := c.Param("uuid") if clientId == "" { - api.RespondError(c, 400, "Client ID is required") + resp.RespondError(c, 400, "Client ID is required") return } results, err := tasks.GetAllTasksResultByUUID(clientId) if err != nil { - api.RespondError(c, 500, "Failed to retrieve tasks: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve tasks: "+err.Error()) return } if len(results) == 0 { - api.RespondError(c, 404, "No tasks found for this client") + resp.RespondError(c, 404, "No tasks found for this client") return } - api.RespondSuccess(c, results) + resp.RespondSuccess(c, results) } diff --git a/internal/api_v1/admin/test/test.go b/internal/api_v1/admin/test/test.go index 34be7fd..84ab2b8 100644 --- a/internal/api_v1/admin/test/test.go +++ b/internal/api_v1/admin/test/test.go @@ -4,7 +4,7 @@ import ( "net" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database/models" "github.com/komari-monitor/komari/internal/geoip" @@ -17,10 +17,10 @@ func TestSendMessage(c *gin.Context) { Message: "This is a test message from Komari.", }) if err != nil { - api.RespondError(c, 500, "Failed to send message: "+err.Error()) + resp.RespondError(c, 500, "Failed to send message: "+err.Error()) return } - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } func TestGeoIp(c *gin.Context) { @@ -34,17 +34,17 @@ func TestGeoIp(c *gin.Context) { } conf, err := conf.GetWithV1Format() if err != nil { - api.RespondError(c, 500, "Failed to get configuration: "+err.Error()) + resp.RespondError(c, 500, "Failed to get configuration: "+err.Error()) return } if !conf.GeoIpEnabled { - api.RespondError(c, 400, "GeoIP is not enabled in the configuration.") + resp.RespondError(c, 400, "GeoIP is not enabled in the configuration.") return } GeoIpRecord, err := geoip.GetGeoInfo(net.ParseIP(ip)) if err != nil { - api.RespondError(c, 500, "Failed to get GeoIP record: "+err.Error()) + resp.RespondError(c, 500, "Failed to get GeoIP record: "+err.Error()) return } - api.RespondSuccess(c, GeoIpRecord) + resp.RespondSuccess(c, GeoIpRecord) } diff --git a/internal/api_v1/admin/theme.go b/internal/api_v1/admin/theme.go index a03c528..54c7d82 100644 --- a/internal/api_v1/admin/theme.go +++ b/internal/api_v1/admin/theme.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" @@ -24,32 +24,32 @@ func UploadTheme(c *gin.Context) { // 读取上传的文件内容 data, err := io.ReadAll(c.Request.Body) if err != nil || len(data) == 0 { - api.RespondError(c, http.StatusBadRequest, "请选择要上传的主题文件") + resp.RespondError(c, http.StatusBadRequest, "请选择要上传的主题文件") return } // 临时文件名 tempFile := filepath.Join(os.TempDir(), "uploaded_theme.zip") if err := os.WriteFile(tempFile, data, 0644); err != nil { - api.RespondError(c, http.StatusInternalServerError, "保存文件失败: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "保存文件失败: "+err.Error()) return } defer os.Remove(tempFile) // 检查文件扩展名(这里假定上传的就是zip) if !strings.HasSuffix(strings.ToLower(tempFile), ".zip") { - api.RespondError(c, http.StatusBadRequest, "只支持ZIP格式的主题文件") + resp.RespondError(c, http.StatusBadRequest, "只支持ZIP格式的主题文件") return } // 解压ZIP文件并验证 themeInfo, err := extractAndValidateTheme(tempFile) if err != nil { - api.RespondError(c, http.StatusBadRequest, err.Error()) + resp.RespondError(c, http.StatusBadRequest, err.Error()) return } - api.RespondSuccessMessage(c, "主题上传成功", themeInfo) + resp.RespondSuccessMessage(c, "主题上传成功", themeInfo) } // ListThemes 列出所有主题 @@ -58,13 +58,13 @@ func ListThemes(c *gin.Context) { // 确保主题目录存在 if _, err := os.Stat(dataDir); os.IsNotExist(err) { - api.RespondSuccess(c, []models.Theme{}) + resp.RespondSuccess(c, []models.Theme{}) return } entries, err := os.ReadDir(dataDir) if err != nil { - api.RespondError(c, http.StatusInternalServerError, "读取主题目录失败: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "读取主题目录失败: "+err.Error()) return } @@ -78,7 +78,7 @@ func ListThemes(c *gin.Context) { } } - api.RespondSuccess(c, themes) + resp.RespondSuccess(c, themes) } // DeleteTheme 删除主题 @@ -88,12 +88,12 @@ func DeleteTheme(c *gin.Context) { } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, "参数错误: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "参数错误: "+err.Error()) return } if req.Short == "default" { - api.RespondError(c, http.StatusBadRequest, "默认主题不能删除") + resp.RespondError(c, http.StatusBadRequest, "默认主题不能删除") return } @@ -101,24 +101,24 @@ func DeleteTheme(c *gin.Context) { // 检查主题是否存在 if _, err := os.Stat(themeDir); os.IsNotExist(err) { - api.RespondError(c, http.StatusNotFound, "主题不存在") + resp.RespondError(c, http.StatusNotFound, "主题不存在") return } // 删除主题目录 if err := os.RemoveAll(themeDir); err != nil { - api.RespondError(c, http.StatusInternalServerError, "删除主题失败: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "删除主题失败: "+err.Error()) return } - api.RespondSuccessMessage(c, "主题删除成功", nil) + resp.RespondSuccessMessage(c, "主题删除成功", nil) } // SetTheme 设置主题 func SetTheme(c *gin.Context) { themeName := c.Query("theme") if themeName == "" { - api.RespondError(c, http.StatusBadRequest, "主题名称不能为空") + resp.RespondError(c, http.StatusBadRequest, "主题名称不能为空") return } @@ -128,7 +128,7 @@ func SetTheme(c *gin.Context) { themeConfigPath := filepath.Join(themeDir, "komari-theme.json") if _, err := os.Stat(themeConfigPath); os.IsNotExist(err) { - api.RespondError(c, http.StatusNotFound, "主题不存在") + resp.RespondError(c, http.StatusNotFound, "主题不存在") return } } @@ -139,11 +139,11 @@ func SetTheme(c *gin.Context) { } if err := conf.Update(updateData); err != nil { - api.RespondError(c, http.StatusInternalServerError, "更新主题设置失败: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "更新主题设置失败: "+err.Error()) return } - api.RespondSuccessMessage(c, "主题设置成功", gin.H{"theme": themeName}) + resp.RespondSuccessMessage(c, "主题设置成功", gin.H{"theme": themeName}) } // extractAndValidateTheme 解压并验证主题 @@ -431,7 +431,7 @@ func UpdateTheme(c *gin.Context) { } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, http.StatusBadRequest, "参数错误: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "参数错误: "+err.Error()) return } @@ -440,14 +440,14 @@ func UpdateTheme(c *gin.Context) { themeConfigPath := filepath.Join(themeDir, "komari-theme.json") if _, err := os.Stat(themeConfigPath); os.IsNotExist(err) { - api.RespondError(c, http.StatusNotFound, "主题不存在") + resp.RespondError(c, http.StatusNotFound, "主题不存在") return } // 加载现有主题配置 themeInfo, err := loadThemeConfig(themeConfigPath) if err != nil { - api.RespondError(c, http.StatusInternalServerError, "读取主题配置失败: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "读取主题配置失败: "+err.Error()) return } @@ -494,14 +494,14 @@ func UpdateTheme(c *gin.Context) { // 相当于: DOWNLOAD_URL=$(curl -s https://api.github.com/repos/owner/repo/releases/latest | jq -r ".assets[0].browser_download_url") gitHubURL, err := getGitHubReleaseDownloadURL(req.GitOwner, req.GitRepo) if err != nil { - api.RespondError(c, http.StatusBadRequest, "从GitHub获取下载链接失败: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "从GitHub获取下载链接失败: "+err.Error()) return } // 使用获取到的链接下载主题 themeData, err = downloadThemeFromURL(gitHubURL) if err != nil { - api.RespondError(c, http.StatusBadRequest, "从GitHub下载主题失败: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "从GitHub下载主题失败: "+err.Error()) return } // 保存下载链接,稍后更新到主题配置中 @@ -515,14 +515,14 @@ func UpdateTheme(c *gin.Context) { // 这里也应用了自动检测GitHub仓库并下载最新release的功能 gitHubURL, err := getGitHubReleaseDownloadURL(owner, repo) if err != nil { - api.RespondError(c, http.StatusBadRequest, "从GitHub获取下载链接失败: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "从GitHub获取下载链接失败: "+err.Error()) return } // 使用获取到的链接下载主题 themeData, err = downloadThemeFromURL(gitHubURL) if err != nil { - api.RespondError(c, http.StatusBadRequest, "从GitHub下载主题失败: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "从GitHub下载主题失败: "+err.Error()) return } // 保存GitHub仓库URL,而不是release下载链接,以便将来可以获取最新版本 @@ -533,7 +533,7 @@ func UpdateTheme(c *gin.Context) { // 新URL不是GitHub仓库地址,直接尝试下载 themeData, err = downloadThemeFromURL(req.URL) if err != nil { - api.RespondError(c, http.StatusBadRequest, "从新URL下载主题失败: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "从新URL下载主题失败: "+err.Error()) return } // downloadURL = req.URL @@ -543,7 +543,7 @@ func UpdateTheme(c *gin.Context) { // 如果没有成功下载主题数据 if themeData == nil || len(themeData) == 0 { - api.RespondError(c, http.StatusBadRequest, "无法下载主题,请提供有效的URL或GitHub仓库信息") + resp.RespondError(c, http.StatusBadRequest, "无法下载主题,请提供有效的URL或GitHub仓库信息") return } @@ -556,7 +556,7 @@ func UpdateTheme(c *gin.Context) { // 临时文件名 tempFile := filepath.Join(os.TempDir(), "downloaded_theme.zip") if err := os.WriteFile(tempFile, themeData, 0644); err != nil { - api.RespondError(c, http.StatusInternalServerError, "保存文件失败: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "保存文件失败: "+err.Error()) return } defer os.Remove(tempFile) @@ -564,7 +564,7 @@ func UpdateTheme(c *gin.Context) { // 解压ZIP文件并验证 updatedThemeInfo, err := extractAndValidateTheme(tempFile) if err != nil { - api.RespondError(c, http.StatusBadRequest, err.Error()) + resp.RespondError(c, http.StatusBadRequest, err.Error()) return } @@ -576,23 +576,23 @@ func UpdateTheme(c *gin.Context) { // updatedConfigPath := filepath.Join("./data/theme", updatedThemeInfo.Short, "komari-theme.json") // updatedConfigData, err := json.MarshalIndent(updatedThemeInfo, "", " ") // if err != nil { - // api.RespondError(c, http.StatusInternalServerError, "生成主题配置失败: "+err.Error()) + // resp.RespondError(c, http.StatusInternalServerError, "生成主题配置失败: "+err.Error()) // return // } // if err := os.WriteFile(updatedConfigPath, updatedConfigData, 0644); err != nil { - // api.RespondError(c, http.StatusInternalServerError, "更新主题配置文件失败: "+err.Error()) + // resp.RespondError(c, http.StatusInternalServerError, "更新主题配置文件失败: "+err.Error()) // return // } // } - api.RespondSuccessMessage(c, "主题更新成功", updatedThemeInfo) + resp.RespondSuccessMessage(c, "主题更新成功", updatedThemeInfo) } func UpdateThemeSettings(c *gin.Context) { theme := c.Query("theme") if theme == "" || theme == "default" { - api.RespondError(c, http.StatusBadRequest, "主题名称不能为空或不能是默认主题") + resp.RespondError(c, http.StatusBadRequest, "主题名称不能为空或不能是默认主题") return } @@ -600,14 +600,14 @@ func UpdateThemeSettings(c *gin.Context) { err := c.ShouldBindJSON(&req) if err != nil { - api.RespondError(c, http.StatusBadRequest, "参数错误: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "参数错误: "+err.Error()) return } db := dbcore.GetDBInstance() data, err := json.Marshal(&req) if err != nil { - api.RespondError(c, http.StatusInternalServerError, "生成主题配置失败: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "生成主题配置失败: "+err.Error()) return } @@ -615,5 +615,5 @@ func UpdateThemeSettings(c *gin.Context) { db.Where("short = ?", theme). Assign(models.ThemeConfiguration{Short: theme, Data: string(data)}). FirstOrCreate(&themeCfg) - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } diff --git a/internal/api_v1/admin/update/favicon.go b/internal/api_v1/admin/update/favicon.go index 29c3643..a94566f 100644 --- a/internal/api_v1/admin/update/favicon.go +++ b/internal/api_v1/admin/update/favicon.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/auditlog" ) @@ -16,31 +16,31 @@ func UploadFavicon(c *gin.Context) { data, err := io.ReadAll(c.Request.Body) if err != nil { if strings.Contains(err.Error(), "request body too large") { - api.RespondError(c, http.StatusRequestEntityTooLarge, "File too large. Maximum size is 5MB") + resp.RespondError(c, http.StatusRequestEntityTooLarge, "File too large. Maximum size is 5MB") } else { - api.RespondError(c, http.StatusBadRequest, err.Error()) + resp.RespondError(c, http.StatusBadRequest, err.Error()) } return } if err := os.WriteFile("./data/favicon.ico", data, 0644); err != nil { - api.RespondError(c, http.StatusInternalServerError, "Failed to save favicon: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to save favicon: "+err.Error()) return } uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "Favicon uploaded", "info") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } func DeleteFavicon(c *gin.Context) { if err := os.Remove("./data/favicon.ico"); err != nil { if os.IsNotExist(err) { - api.RespondError(c, http.StatusNotFound, "Favicon not found") + resp.RespondError(c, http.StatusNotFound, "Favicon not found") } else { - api.RespondError(c, http.StatusInternalServerError, "Failed to delete favicon: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to delete favicon: "+err.Error()) } return } uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "Favicon deleted", "info") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } diff --git a/internal/api_v1/admin/update/geoip.go b/internal/api_v1/admin/update/geoip.go index 7e6afc9..1e331e0 100644 --- a/internal/api_v1/admin/update/geoip.go +++ b/internal/api_v1/admin/update/geoip.go @@ -2,17 +2,17 @@ package update import ( "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/auditlog" "github.com/komari-monitor/komari/internal/geoip" ) func UpdateMmdbGeoIP(c *gin.Context) { if err := geoip.UpdateDatabase(); err != nil { - api.RespondError(c, 500, "Failed to update GeoIP database "+err.Error()) + resp.RespondError(c, 500, "Failed to update GeoIP database "+err.Error()) return } uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "GeoIP database updated", "info") - api.RespondSuccess(c, nil) + resp.RespondSuccess(c, nil) } diff --git a/internal/api_v1/admin/update/user.go b/internal/api_v1/admin/update/user.go index 53c44aa..f7c0ed3 100644 --- a/internal/api_v1/admin/update/user.go +++ b/internal/api_v1/admin/update/user.go @@ -2,7 +2,7 @@ package update import ( "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/accounts" "github.com/komari-monitor/komari/internal/database/auditlog" ) @@ -15,26 +15,26 @@ func UpdateUser(c *gin.Context) { SsoType *string `json:"sso_type"` } if err := c.ShouldBindJSON(&req); err != nil { - api.RespondError(c, 400, "Invalid or missing request body: "+err.Error()) + resp.RespondError(c, 400, "Invalid or missing request body: "+err.Error()) return } if req.Password == nil && req.Name == nil { - api.RespondError(c, 400, "At least one field (username or password) must be provided") + resp.RespondError(c, 400, "At least one field (username or password) must be provided") return } if req.Name != nil && len(*req.Name) < 3 { - api.RespondError(c, 400, "Username must be at least 3 characters long") + resp.RespondError(c, 400, "Username must be at least 3 characters long") return } if req.Password != nil && len(*req.Password) < 6 { - api.RespondError(c, 400, "Password must be at least 6 characters long") + resp.RespondError(c, 400, "Password must be at least 6 characters long") return } if err := accounts.UpdateUser(req.Uuid, req.Name, req.Password, req.SsoType); err != nil { - api.RespondError(c, 500, "Failed to update user: "+err.Error()) + resp.RespondError(c, 500, "Failed to update user: "+err.Error()) return } uuid, _ := c.Get("uuid") auditlog.Log(c.ClientIP(), uuid.(string), "User updated", "warn") - api.RespondSuccess(c, gin.H{"uuid": req.Uuid}) + resp.RespondSuccess(c, gin.H{"uuid": req.Uuid}) } diff --git a/internal/api_v1/admin/upload.go b/internal/api_v1/admin/upload.go index 0434af1..3ac995e 100644 --- a/internal/api_v1/admin/upload.go +++ b/internal/api_v1/admin/upload.go @@ -13,7 +13,7 @@ import ( "time" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" ) // 只有一个备份恢复操作在进行 @@ -23,7 +23,7 @@ var restoreMutex sync.Mutex func UploadBackup(c *gin.Context) { // 尝试获取锁,如果已有恢复操作在进行,则立即返回错误 if !restoreMutex.TryLock() { - api.RespondError(c, http.StatusConflict, "Another restore operation is already in progress") + resp.RespondError(c, http.StatusConflict, "Another restore operation is already in progress") return } defer restoreMutex.Unlock() @@ -31,27 +31,27 @@ func UploadBackup(c *gin.Context) { // 获取上传的文件 file, header, err := c.Request.FormFile("backup") if err != nil { - api.RespondError(c, http.StatusBadRequest, fmt.Sprintf("Error getting uploaded file: %v", err)) + resp.RespondError(c, http.StatusBadRequest, fmt.Sprintf("Error getting uploaded file: %v", err)) return } defer file.Close() // 检查文件是否为zip格式 if !strings.HasSuffix(strings.ToLower(header.Filename), ".zip") { - api.RespondError(c, http.StatusBadRequest, "Uploaded file must be a ZIP archive") + resp.RespondError(c, http.StatusBadRequest, "Uploaded file must be a ZIP archive") return } // 确保data目录存在 if err := os.MkdirAll("./data", 0755); err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating data directory: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating data directory: %v", err)) return } // 创建临时文件保存上传的zip(先校验,再落地到固定位置) tempFile, err := os.CreateTemp("", "backup-upload-*.zip") if err != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating temporary file: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating temporary file: %v", err)) return } tempFilePath := tempFile.Name() @@ -61,7 +61,7 @@ func UploadBackup(c *gin.Context) { _, err = io.Copy(tempFile, file) if err != nil { tempFile.Close() - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error saving uploaded file: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error saving uploaded file: %v", err)) return } tempFile.Close() // 关闭文件以便后续操作 @@ -77,11 +77,11 @@ func UploadBackup(c *gin.Context) { } zr.Close() if !hasMarkup { - api.RespondError(c, http.StatusBadRequest, "Invalid backup file: missing komari-backup-markup file") + resp.RespondError(c, http.StatusBadRequest, "Invalid backup file: missing komari-backup-markup file") return } } else { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error opening zip file: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error opening zip file: %v", err)) return } @@ -93,18 +93,18 @@ func UploadBackup(c *gin.Context) { // fallback:拷贝 in, err2 := os.Open(tempFilePath) if err2 != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error preparing backup file: %v", err)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error preparing backup file: %v", err)) return } defer in.Close() out, err2 := os.Create(finalPath) if err2 != nil { - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating target backup file: %v", err2)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating target backup file: %v", err2)) return } if _, err2 = io.Copy(out, in); err2 != nil { out.Close() - api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error writing target backup file: %v", err2)) + resp.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error writing target backup file: %v", err2)) return } out.Close() diff --git a/internal/api_v1/client/autoDiscovery.go b/internal/api_v1/client/autoDiscovery.go index 1bd3806..b90f725 100644 --- a/internal/api_v1/client/autoDiscovery.go +++ b/internal/api_v1/client/autoDiscovery.go @@ -3,7 +3,7 @@ package client import ( "github.com/gin-gonic/gin" "github.com/gookit/event" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database/clients" "github.com/komari-monitor/komari/internal/eventType" @@ -13,12 +13,12 @@ import ( func RegisterClient(c *gin.Context) { auth := c.GetHeader("Authorization") if auth == "" { - api.RespondError(c, 403, "Invalid AutoDiscovery Key") + resp.RespondError(c, 403, "Invalid AutoDiscovery Key") return } cfg, err := conf.GetWithV1Format() if err != nil { - api.RespondError(c, 500, "Failed to get configuration: "+err.Error()) + resp.RespondError(c, 500, "Failed to get configuration: "+err.Error()) return } @@ -26,7 +26,7 @@ func RegisterClient(c *gin.Context) { len(cfg.AutoDiscoveryKey) < 12 || "Bearer "+cfg.AutoDiscoveryKey != auth { - api.RespondError(c, 403, "Invalid AutoDiscovery Key") + resp.RespondError(c, 403, "Invalid AutoDiscovery Key") return } name := c.Query("name") @@ -36,12 +36,12 @@ func RegisterClient(c *gin.Context) { name = "Auto-" + name uuid, token, err := clients.CreateClientWithName(name) if err != nil { - api.RespondError(c, 500, "Failed to create client: "+err.Error()) + resp.RespondError(c, 500, "Failed to create client: "+err.Error()) return } event.Trigger(eventType.ClientCreated, event.M{ "client": uuid, "token": token, }) - api.RespondSuccess(c, gin.H{"uuid": uuid, "token": token}) + resp.RespondSuccess(c, gin.H{"uuid": uuid, "token": token}) } diff --git a/internal/api_v1/client/report.go b/internal/api_v1/client/report.go index 10c8e2b..8f1283b 100644 --- a/internal/api_v1/client/report.go +++ b/internal/api_v1/client/report.go @@ -13,7 +13,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gookit/event" "github.com/gorilla/websocket" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/vars" "github.com/komari-monitor/komari/internal/common" "github.com/komari-monitor/komari/internal/database/clients" "github.com/komari-monitor/komari/internal/database/models" @@ -217,7 +217,7 @@ func processMessage(conn *ws.SafeConn, message []byte, uuid string) { } func SaveClientReport(uuid string, report common.Report) error { - reports, _ := api.Records.Get(uuid) + reports, _ := vars.Records.Get(uuid) if reports == nil { reports = []common.Report{} } @@ -225,7 +225,7 @@ func SaveClientReport(uuid string, report common.Report) error { report.CPU.Usage = 0.01 } reports = append(reports.([]common.Report), report) - api.Records.Set(uuid, reports, cache.DefaultExpiration) + vars.Records.Set(uuid, reports, cache.DefaultExpiration) return nil } diff --git a/internal/api_v1/client/terminal.go b/internal/api_v1/client/terminal.go index ff53774..eccb7b7 100644 --- a/internal/api_v1/client/terminal.go +++ b/internal/api_v1/client/terminal.go @@ -5,12 +5,12 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/terminal" ) func EstablishConnection(c *gin.Context) { session_id := c.Query("id") - session, exists := api.TerminalSessions[session_id] + session, exists := terminal.TerminalSessions[session_id] if !exists || session == nil || session.Browser == nil { c.JSON(404, gin.H{"status": "error", "error": "Session not found"}) return @@ -27,22 +27,22 @@ func EstablishConnection(c *gin.Context) { } conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { - api.TerminalSessionsMutex.Lock() + terminal.TerminalSessionsMutex.Lock() if session.Browser != nil { session.Browser.Close() } - delete(api.TerminalSessions, session_id) - api.TerminalSessionsMutex.Unlock() + delete(terminal.TerminalSessions, session_id) + terminal.TerminalSessionsMutex.Unlock() return } session.Agent = conn conn.SetCloseHandler(func(code int, text string) error { - delete(api.TerminalSessions, session_id) + delete(terminal.TerminalSessions, session_id) // 通知 Browser 关闭终端连接 if session.Browser != nil { session.Browser.Close() } return nil }) - go api.ForwardTerminal(session_id) + go terminal.ForwardTerminal(session_id) } diff --git a/internal/internal.go b/internal/api_v1/init.go similarity index 83% rename from internal/internal.go rename to internal/api_v1/init.go index 68d82dc..9e2d755 100644 --- a/internal/internal.go +++ b/internal/api_v1/init.go @@ -1,8 +1,8 @@ -package internal +package api_v1 import ( "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/gookit/event" "github.com/komari-monitor/komari/internal/api_v1/admin" "github.com/komari-monitor/komari/internal/api_v1/admin/clipboard" log_api "github.com/komari-monitor/komari/internal/api_v1/admin/log" @@ -11,10 +11,27 @@ import ( "github.com/komari-monitor/komari/internal/api_v1/admin/update" "github.com/komari-monitor/komari/internal/api_v1/client" "github.com/komari-monitor/komari/internal/api_v1/record" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/api_v1/task" + "github.com/komari-monitor/komari/internal/api_v1/terminal" "github.com/komari-monitor/komari/internal/conf" + "github.com/komari-monitor/komari/internal/eventType" ) +func init() { + event.On(eventType.ServerInitializeStart, event.ListenerFunc(func(e event.Event) error { + r := e.Get("engine").(*gin.Engine) + config, _ := conf.GetWithV1Format() + LoadApiV1Routes(r, config) + return nil + }), event.Normal) + + event.On(eventType.SchedulerEveryMinute, event.ListenerFunc(func(e event.Event) error { + SaveClientReportToDB() + return nil + })) +} + func LoadApiV1Routes(r *gin.Engine, conf conf.V1Struct) { r.Use(func(c *gin.Context) { if len(c.Request.URL.Path) >= 4 && c.Request.URL.Path[:4] == "/api" { @@ -23,30 +40,29 @@ func LoadApiV1Routes(r *gin.Engine, conf conf.V1Struct) { c.Next() }) - r.Use(api.PrivateSiteMiddleware()) + r.Use(PrivateSiteMiddleware()) r.Any("/ping", func(c *gin.Context) { c.String(200, "pong") }) // #region 公开路由 - r.POST("/api/login", api.Login) - r.GET("/api/me", api.GetMe) - r.GET("/api/clients", api.GetClients) - r.GET("/api/nodes", api.GetNodesInformation) - r.GET("/api/public", api.GetPublicSettings) - r.GET("/api/oauth", api.OAuth) - r.GET("/api/oauth_callback", api.OAuthCallback) - r.GET("/api/logout", api.Logout) - r.GET("/api/version", api.GetVersion) - r.GET("/api/recent/:uuid", api.GetClientRecentRecords) - + r.POST("/api/login", Login) + r.GET("/api/me", GetMe) + r.GET("/api/clients", GetClients) + r.GET("/api/nodes", GetNodesInformation) + r.GET("/api/public", GetPublicSettings) + r.GET("/api/oauth", OAuth) + r.GET("/api/oauth_callback", OAuthCallback) + r.GET("/api/logout", Logout) + r.GET("/api/version", resp.GetVersion) + r.GET("/api/recent/:uuid", GetClientRecentRecords) r.GET("/api/records/load", record.GetRecordsByUUID) r.GET("/api/records/ping", record.GetPingRecords) r.GET("/api/task/ping", task.GetPublicPingTasks) // #region Agent r.POST("/api/clients/register", client.RegisterClient) - tokenAuthrized := r.Group("/api/clients", api.TokenAuthMiddleware()) + tokenAuthrized := r.Group("/api/clients", TokenAuthMiddleware()) { tokenAuthrized.GET("/report", client.WebSocketReport) // websocket tokenAuthrized.POST("/uploadBasicInfo", client.UploadBasicInfo) @@ -55,7 +71,7 @@ func LoadApiV1Routes(r *gin.Engine, conf conf.V1Struct) { tokenAuthrized.POST("/task/result", client.TaskResult) } // #region 管理员 - adminAuthrized := r.Group("/api/admin", api.AdminAuthMiddleware()) + adminAuthrized := r.Group("/api/admin", AdminAuthMiddleware()) { adminAuthrized.GET("/download/backup", admin.DownloadBackup) adminAuthrized.POST("/upload/backup", admin.UploadBackup) @@ -114,7 +130,7 @@ func LoadApiV1Routes(r *gin.Engine, conf conf.V1Struct) { clientGroup.GET("/:uuid/token", admin.GetClientToken) clientGroup.POST("/order", admin.OrderWeight) // client terminal - clientGroup.GET("/:uuid/terminal", api.RequestTerminal) + clientGroup.GET("/:uuid/terminal", terminal.RequestTerminal) } // records diff --git a/internal/api_v1/latestRecord.go b/internal/api_v1/latestRecord.go index 1e5f01e..57f33d9 100644 --- a/internal/api_v1/latestRecord.go +++ b/internal/api_v1/latestRecord.go @@ -2,6 +2,8 @@ package api_v1 import ( "github.com/gin-gonic/gin" + "github.com/komari-monitor/komari/internal/api_v1/resp" + "github.com/komari-monitor/komari/internal/api_v1/vars" "github.com/komari-monitor/komari/internal/database/accounts" "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" @@ -11,7 +13,7 @@ func GetClientRecentRecords(c *gin.Context) { uuid := c.Param("uuid") if uuid == "" { - RespondError(c, 400, "UUID is required") + resp.RespondError(c, 400, "UUID is required") return } @@ -34,11 +36,11 @@ func GetClientRecentRecords(c *gin.Context) { } if hiddenMap[uuid] { - RespondError(c, 400, "UUID is required") //防止未登录用户获取隐藏客户端数据 + resp.RespondError(c, 400, "UUID is required") //防止未登录用户获取隐藏客户端数据 return } } - records, _ := Records.Get(uuid) - RespondSuccess(c, records) + records, _ := vars.Records.Get(uuid) + resp.RespondSuccess(c, records) } diff --git a/internal/api_v1/login.go b/internal/api_v1/login.go index 2ec1195..979d6fe 100644 --- a/internal/api_v1/login.go +++ b/internal/api_v1/login.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database/accounts" "github.com/komari-monitor/komari/internal/database/auditlog" @@ -23,29 +24,29 @@ type LoginRequest struct { func Login(c *gin.Context) { conf, _ := conf.GetWithV1Format() if conf.DisablePasswordLogin { - RespondError(c, http.StatusForbidden, "Password login is disabled") + resp.RespondError(c, http.StatusForbidden, "Password login is disabled") return } bodyBytes, err := io.ReadAll(c.Request.Body) if err != nil { - RespondError(c, http.StatusBadRequest, "Invalid request body: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "Invalid request body: "+err.Error()) return } var data LoginRequest err = json.Unmarshal(bodyBytes, &data) if err != nil { - RespondError(c, http.StatusBadRequest, "Invalid request body: "+err.Error()) + resp.RespondError(c, http.StatusBadRequest, "Invalid request body: "+err.Error()) return } if data.Username == "" || data.Password == "" { - RespondError(c, http.StatusBadRequest, "Invalid request body: Username and password are required") + resp.RespondError(c, http.StatusBadRequest, "Invalid request body: Username and password are required") return } uuid, success := accounts.CheckPassword(data.Username, data.Password) if !success { - RespondError(c, http.StatusUnauthorized, "Invalid credentials") + resp.RespondError(c, http.StatusUnauthorized, "Invalid credentials") event.Trigger(eventType.LoginFailed, event.M{ "username": data.Username, "method": "password", @@ -61,23 +62,23 @@ func Login(c *gin.Context) { user, _ := accounts.GetUserByUUID(uuid) if user.TwoFactor != "" { // 开启了2FA if data.TwoFa == "" { - RespondError(c, http.StatusUnauthorized, "2FA code is required") + resp.RespondError(c, http.StatusUnauthorized, "2FA code is required") return } if ok, err := accounts.Verify2Fa(uuid, data.TwoFa); err != nil || !ok { - RespondError(c, http.StatusUnauthorized, "Invalid 2FA code") + resp.RespondError(c, http.StatusUnauthorized, "Invalid 2FA code") return } } // Create session session, err := accounts.CreateSession(uuid, 2592000, c.Request.UserAgent(), c.ClientIP(), "password") if err != nil { - RespondError(c, http.StatusInternalServerError, "Failed to create session: "+err.Error()) + resp.RespondError(c, http.StatusInternalServerError, "Failed to create session: "+err.Error()) return } c.SetCookie("session_token", session, 2592000, "/", "", false, true) auditlog.Log(c.ClientIP(), uuid, "logged in (password)", "login") - RespondSuccess(c, gin.H{"set-cookie": gin.H{"session_token": session}}) + resp.RespondSuccess(c, gin.H{"set-cookie": gin.H{"session_token": session}}) event.Trigger(eventType.UserLogin, event.M{ "username": data.Username, "method": "password", diff --git a/internal/api_v1/nodes.go b/internal/api_v1/nodes.go index e158b7b..051ff3a 100644 --- a/internal/api_v1/nodes.go +++ b/internal/api_v1/nodes.go @@ -2,6 +2,7 @@ package api_v1 import ( "github.com/gin-gonic/gin" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/accounts" "github.com/komari-monitor/komari/internal/database/clients" ) @@ -9,7 +10,7 @@ import ( func GetNodesInformation(c *gin.Context) { clientList, err := clients.GetAllClientBasicInfo() if err != nil { - RespondError(c, 500, "Failed to retrieve client information: "+err.Error()) + resp.RespondError(c, 500, "Failed to retrieve client information: "+err.Error()) return } isLogin := false @@ -35,5 +36,5 @@ func GetNodesInformation(c *gin.Context) { } clientList = clientList[:j] - RespondSuccess(c, clientList) + resp.RespondSuccess(c, clientList) } diff --git a/internal/api_v1/public.go b/internal/api_v1/public.go index 101c713..1aa6ca0 100644 --- a/internal/api_v1/public.go +++ b/internal/api_v1/public.go @@ -2,14 +2,15 @@ package api_v1 import ( "github.com/gin-gonic/gin" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database" ) func GetPublicSettings(c *gin.Context) { p, e := database.GetPublicInfo() if e != nil { - RespondError(c, 500, e.Error()) + resp.RespondError(c, 500, e.Error()) return } - RespondSuccess(c, p) + resp.RespondSuccess(c, p) } diff --git a/internal/api_v1/record/record.go b/internal/api_v1/record/record.go index fbc92a4..e553fe1 100644 --- a/internal/api_v1/record/record.go +++ b/internal/api_v1/record/record.go @@ -6,7 +6,7 @@ import ( "time" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/accounts" "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" @@ -37,14 +37,14 @@ func GetRecordsByUUID(c *gin.Context) { } if hiddenMap[uuid] { - api.RespondError(c, 400, "UUID is required") //防止未登录用户获取隐藏客户端数据 + resp.RespondError(c, 400, "UUID is required") //防止未登录用户获取隐藏客户端数据 return } } hours := c.Query("hours") if uuid == "" { - api.RespondError(c, 400, "UUID is required") + resp.RespondError(c, 400, "UUID is required") return } if hours == "" { @@ -53,7 +53,7 @@ func GetRecordsByUUID(c *gin.Context) { hoursInt, err := strconv.Atoi(hours) if err != nil { - api.RespondError(c, 400, "Invalid hours parameter") + resp.RespondError(c, 400, "Invalid hours parameter") return } @@ -65,13 +65,13 @@ func GetRecordsByUUID(c *gin.Context) { } if !validLoadTypes[loadType] { - api.RespondError(c, 400, "Invalid load_type parameter") + resp.RespondError(c, 400, "Invalid load_type parameter") return } clientRecords, err := records.GetRecordsByClientAndTime(uuid, time.Now().Add(-time.Duration(hoursInt)*time.Hour), time.Now()) if err != nil { - api.RespondError(c, 500, "Failed to fetch records: "+err.Error()) + resp.RespondError(c, 500, "Failed to fetch records: "+err.Error()) return } @@ -125,7 +125,7 @@ func GetRecordsByUUID(c *gin.Context) { } } - api.RespondSuccess(c, response) + resp.RespondSuccess(c, response) } // filterRecordsByLoadType 根据 load_type 过滤记录,只返回相关字段 @@ -195,7 +195,7 @@ func GetPingRecords(c *gin.Context) { // 必须提供 uuid 或 task_id 其中至少一个 if uuid == "" && taskIdStr == "" { - api.RespondError(c, 400, "UUID or task_id is required") + resp.RespondError(c, 400, "UUID or task_id is required") return } @@ -242,7 +242,7 @@ func GetPingRecords(c *gin.Context) { } if uuid != "" { if hiddenMap[uuid] { - api.RespondSuccess(c, response) // 对于尝试获取隐藏uuid一键哈气 + resp.RespondSuccess(c, response) // 对于尝试获取隐藏uuid一键哈气 return } } @@ -267,7 +267,7 @@ func GetPingRecords(c *gin.Context) { if taskIdStr != "" { taskId, err = strconv.Atoi(taskIdStr) if err != nil { - api.RespondError(c, 400, "Invalid task_id parameter") + resp.RespondError(c, 400, "Invalid task_id parameter") return } } @@ -275,7 +275,7 @@ func GetPingRecords(c *gin.Context) { // 查询记录,现在支持 uuid + task_id 组合查询 records, err = tasks.GetPingRecords(uuid, taskId, startTime, endTime) if err != nil { - api.RespondError(c, 500, "Failed to fetch ping records: "+err.Error()) + resp.RespondError(c, 500, "Failed to fetch ping records: "+err.Error()) return } @@ -357,7 +357,7 @@ func GetPingRecords(c *gin.Context) { // 获取所有 pingTasks pingTasks, err := tasks.GetAllPingTasks() if err != nil { - api.RespondError(c, 500, "Failed to fetch ping tasks: "+err.Error()) + resp.RespondError(c, 500, "Failed to fetch ping tasks: "+err.Error()) return } @@ -443,5 +443,5 @@ func GetPingRecords(c *gin.Context) { } response.Count = len(response.Records) // 计算最后结果保持计数一致 - api.RespondSuccess(c, response) + resp.RespondSuccess(c, response) } diff --git a/internal/api_v1/resp/resp.go b/internal/api_v1/resp/resp.go new file mode 100644 index 0000000..53ddf7b --- /dev/null +++ b/internal/api_v1/resp/resp.go @@ -0,0 +1,40 @@ +package resp + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/komari-monitor/komari/internal/conf" +) + +type Response struct { + Status string `json:"status"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +// Respond sends a standardized JSON response. +func Respond(c *gin.Context, httpStatus int, status string, message string, data interface{}) { + c.JSON(httpStatus, Response{Status: status, Message: message, Data: data}) +} + +// RespondSuccess sends a success response with data. +func RespondSuccess(c *gin.Context, data interface{}) { + Respond(c, http.StatusOK, "success", "", data) +} + +// RespondSuccessMessage sends a success response with message and data. +func RespondSuccessMessage(c *gin.Context, message string, data interface{}) { + Respond(c, http.StatusOK, "success", message, data) +} + +// RespondError sends an error response with message. +func RespondError(c *gin.Context, httpStatus int, message string) { + Respond(c, httpStatus, "error", message, nil) +} +func GetVersion(c *gin.Context) { + RespondSuccess(c, gin.H{ + "version": conf.Version, + "hash": conf.CommitHash, + }) +} diff --git a/internal/api_v1/task/ping.go b/internal/api_v1/task/ping.go index c3ca3e2..4e80839 100644 --- a/internal/api_v1/task/ping.go +++ b/internal/api_v1/task/ping.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - api "github.com/komari-monitor/komari/internal/api_v1" + "github.com/komari-monitor/komari/internal/api_v1/resp" "github.com/komari-monitor/komari/internal/database/tasks" ) @@ -19,7 +19,7 @@ type PublicPingTask struct { func GetPublicPingTasks(c *gin.Context) { tasks, err := tasks.GetAllPingTasks() if err != nil { - api.RespondError(c, http.StatusInternalServerError, err.Error()) + resp.RespondError(c, http.StatusInternalServerError, err.Error()) return } @@ -34,5 +34,5 @@ func GetPublicPingTasks(c *gin.Context) { } } - api.RespondSuccess(c, publicTasks) + resp.RespondSuccess(c, publicTasks) } diff --git a/internal/api_v1/terminal.go b/internal/api_v1/terminal/terminal.go similarity index 93% rename from internal/api_v1/terminal.go rename to internal/api_v1/terminal/terminal.go index 231cc71..c86508a 100644 --- a/internal/api_v1/terminal.go +++ b/internal/api_v1/terminal/terminal.go @@ -1,18 +1,32 @@ -package api_v1 +package terminal import ( + "sync" + "log" "net/http" "time" - "github.com/gin-gonic/gin" "github.com/gorilla/websocket" + + "github.com/gin-gonic/gin" "github.com/komari-monitor/komari/internal/database/auditlog" "github.com/komari-monitor/komari/internal/database/clients" "github.com/komari-monitor/komari/internal/ws" "github.com/komari-monitor/komari/pkg/utils" ) +type TerminalSession struct { + UUID string + UserUUID string + Browser *websocket.Conn + Agent *websocket.Conn + RequesterIp string +} + +var TerminalSessionsMutex = &sync.Mutex{} +var TerminalSessions = make(map[string]*TerminalSession) + func RequestTerminal(c *gin.Context) { uuid := c.Param("uuid") user_uuid, _ := c.Get("uuid") diff --git a/internal/api_v1/vars/vars.go b/internal/api_v1/vars/vars.go new file mode 100644 index 0000000..4c0636c --- /dev/null +++ b/internal/api_v1/vars/vars.go @@ -0,0 +1,11 @@ +package vars + +import ( + "time" + + "github.com/patrickmn/go-cache" +) + +var ( + Records = cache.New(1*time.Minute, 1*time.Minute) +) diff --git a/pkg/cloudflared/cloudflared.go b/internal/cloudflared/cloudflared.go similarity index 100% rename from pkg/cloudflared/cloudflared.go rename to internal/cloudflared/cloudflared.go diff --git a/pkg/cloudflared/cloudflared_test.go b/internal/cloudflared/cloudflared_test.go similarity index 85% rename from pkg/cloudflared/cloudflared_test.go rename to internal/cloudflared/cloudflared_test.go index abc1013..afd849e 100644 --- a/pkg/cloudflared/cloudflared_test.go +++ b/internal/cloudflared/cloudflared_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/komari-monitor/komari/pkg/cloudflared" + "github.com/komari-monitor/komari/internal/cloudflared" ) func TestRunCloudflared(t *testing.T) { diff --git a/internal/cloudflared/init.go b/internal/cloudflared/init.go new file mode 100644 index 0000000..907a7ab --- /dev/null +++ b/internal/cloudflared/init.go @@ -0,0 +1,28 @@ +package cloudflared + +import ( + "fmt" + "os" + "strings" + + "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + event.On(eventType.ServerInitializeStart, event.ListenerFunc(func(e event.Event) error { + if strings.ToLower(strings.ToLower(os.Getenv("KOMARI_ENABLE_CLOUDFLARED"))) == "true" { + err := RunCloudflared() + if err != nil { + // Error in ServerInitializeStart will cause the process to exit + return fmt.Errorf("failed to run cloudflared: %v", err) + } + } + return nil + })) + + event.On(eventType.ProcessExit, event.ListenerFunc(func(e event.Event) error { + Kill() + return nil + })) +} diff --git a/internal/conf/config.go b/internal/conf/config.go index 9fb6547..a3a5569 100644 --- a/internal/conf/config.go +++ b/internal/conf/config.go @@ -2,10 +2,14 @@ package conf import ( "encoding/json" + "fmt" "os" + "log/slog" + "github.com/gookit/event" "github.com/komari-monitor/komari/cmd/flags" + "github.com/komari-monitor/komari/internal/database/auditlog" "github.com/komari-monitor/komari/internal/eventType" ) @@ -38,17 +42,25 @@ func Override(cst Config) error { if err != nil { return err } - if err := os.WriteFile(flags.ConfigFile, b, 0644); err != nil { - return err - } oldConf := *Conf Conf = &cst - event.Trigger(eventType.ConfigUpdated, event.M{ + err, _ = event.Trigger(eventType.ConfigUpdated, event.M{ "old": oldConf, "new": cst, }) - return nil + if err != nil { + // 撤回配置修改 + Conf = &oldConf + b, _ = json.MarshalIndent(oldConf, "", " ") + + auditlog.EventLog("error", fmt.Sprintf("Configuration update reverted due to error in ConfigUpdated event: %v", err)) + slog.Error("Configuration update reverted due to error in ConfigUpdated event.", slog.Any("error", err)) + } + if err := os.WriteFile(flags.ConfigFile, b, 0644); err != nil { + return err + } + return err } func SavePartial(cst map[string]interface{}) error { @@ -96,19 +108,6 @@ func SaveFull(cst Config) error { return Override(cst) } -func Load() (*Config, error) { - b, err := os.ReadFile(flags.ConfigFile) - if err != nil { - return nil, err - } - cst := &Config{} - if err := json.Unmarshal(b, cst); err != nil { - return nil, err - } - Conf = cst - return cst, nil -} - // GetWithV1Format 以 v1 API 格式获取配置对象,使用 Conf 直接获取对象引用 func GetWithV1Format() (V1Struct, error) { return Conf.ToV1Format(), nil @@ -249,3 +248,21 @@ func deepMerge(dst, src map[string]interface{}) map[string]interface{} { } return dst } + +// FromEvent 从事件对象中提取旧配置和新配置 returns (old,new,error)。 +func FromEvent(e event.Event) (Config, Config, error) { + oldVal := e.Get("old") + newVal := e.Get("new") + + oldConf, ok := oldVal.(Config) + if !ok { + return Config{}, Config{}, fmt.Errorf("FromEvent: 'old' key value is not of type Config. Got: %T", oldVal) + } + + newConf, ok := newVal.(Config) + if !ok { + return Config{}, Config{}, fmt.Errorf("FromEvent: 'new' key value is not of type Config. Got: %T", newVal) + } + + return oldConf, newConf, nil +} diff --git a/internal/conf/init.go b/internal/conf/init.go new file mode 100644 index 0000000..bb75cda --- /dev/null +++ b/internal/conf/init.go @@ -0,0 +1,34 @@ +package conf + +import ( + "encoding/json" + "os" + + "github.com/gookit/event" + "github.com/komari-monitor/komari/cmd/flags" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + Conf = &Config{} + event.On(eventType.ProcessStart, event.ListenerFunc(func(e event.Event) error { + b, err := os.ReadFile(flags.ConfigFile) + if err != nil { + return err + } + cst := &Config{} + if err := json.Unmarshal(b, cst); err != nil { + return err + } + Conf = cst + return nil + }), event.Max+2) + + event.On(eventType.ServerInitializeDone, event.ListenerFunc(func(e event.Event) error { + event.Trigger(eventType.ConfigUpdated, event.M{ + "old": Config{}, + "new": *Conf, + }) + return nil + }), event.Low) +} diff --git a/internal/database/accounts/init.go b/internal/database/accounts/init.go new file mode 100644 index 0000000..fbc71da --- /dev/null +++ b/internal/database/accounts/init.go @@ -0,0 +1,24 @@ +package accounts + +import ( + "log" + + "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/database/dbcore" + "github.com/komari-monitor/komari/internal/database/models" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + event.On(eventType.ServerInitializeStart, event.ListenerFunc(func(e event.Event) error { + var count int64 = 0 + if dbcore.GetDBInstance().Model(&models.User{}).Count(&count); count == 0 { + user, passwd, err := CreateDefaultAdminAccount() + if err != nil { + return err + } + log.Println("Default admin account created. Username:", user, ", Password:", passwd) + } + return nil + })) +} diff --git a/internal/database/auditlog/log.go b/internal/database/auditlog/log.go index e8d8b4c..ccc303d 100644 --- a/internal/database/auditlog/log.go +++ b/internal/database/auditlog/log.go @@ -1,6 +1,7 @@ package auditlog import ( + "fmt" "log" "time" @@ -25,6 +26,14 @@ func EventLog(eventType, message string) { Log("", "", message, eventType) } +func InternalInfof(message string, v ...interface{}) { + Log("", "", fmt.Sprintf(message, v...), "info") +} + +func InternalErrorf(message string, v ...interface{}) { + Log("", "", fmt.Sprintf(message, v...), "error") +} + // Delete logs older than 30 days func RemoveOldLogs() { db := dbcore.GetDBInstance() diff --git a/internal/database/dbcore/dbcore.go b/internal/database/dbcore/dbcore.go index fa3fbac..df1369f 100644 --- a/internal/database/dbcore/dbcore.go +++ b/internal/database/dbcore/dbcore.go @@ -5,8 +5,10 @@ import ( "log" "sync" + "github.com/gookit/event" "github.com/komari-monitor/komari/cmd/flags" "github.com/komari-monitor/komari/internal/database/models" + "github.com/komari-monitor/komari/internal/eventType" logutil "github.com/komari-monitor/komari/internal/log" "gorm.io/driver/mysql" "gorm.io/driver/sqlite" @@ -106,3 +108,10 @@ func GetDBInstance() *gorm.DB { }) return instance } + +func init() { + event.On(eventType.SchedulerEvery5Minutes, event.ListenerFunc(func(e event.Event) error { + instance.Exec("PRAGMA wal_checkpoint(TRUNCATE);") + return nil + })) +} diff --git a/internal/database/dbcore/init.go b/internal/database/dbcore/init.go new file mode 100644 index 0000000..2984c3f --- /dev/null +++ b/internal/database/dbcore/init.go @@ -0,0 +1,13 @@ +package dbcore + +import ( + "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + event.On(eventType.ProcessStart, event.ListenerFunc(func(e event.Event) error { + _ = GetDBInstance() + return nil + }), event.Max+1) +} diff --git a/internal/database/notification/load.go b/internal/database/notification/load.go index a377940..784b194 100644 --- a/internal/database/notification/load.go +++ b/internal/database/notification/load.go @@ -59,10 +59,5 @@ func SaveLoadNotification(record models.LoadNotification) error { } func ReloadLoadNotificationSchedule() error { - db := dbcore.GetDBInstance() - var loadNotifications []models.LoadNotification - if err := db.Find(&loadNotifications).Error; err != nil { - return err - } - return notifier.ReloadLoadNotificationSchedule(loadNotifications) + return notifier.ReloadLoadNotification() } diff --git a/internal/database/tasks/ping.go b/internal/database/tasks/ping.go index e58d66e..6104dbc 100644 --- a/internal/database/tasks/ping.go +++ b/internal/database/tasks/ping.go @@ -84,13 +84,9 @@ func DeleteAllPingRecords() error { } return result.Error } + func ReloadPingSchedule() error { - db := dbcore.GetDBInstance() - var pingTasks []models.PingTask - if err := db.Find(&pingTasks).Error; err != nil { - return err - } - return pingSchedule.ReloadPingSchedule(pingTasks) + return pingSchedule.ReloadPingSchedule() } func GetPingRecords(uuid string, taskId int, start, end time.Time) ([]models.PingRecord, error) { diff --git a/internal/eventType/events.go b/internal/eventType/events.go index 3afc4b9..5b44725 100644 --- a/internal/eventType/events.go +++ b/internal/eventType/events.go @@ -21,12 +21,12 @@ const ( LoginFailed = "user.login.failed" // 用户登录失败 - ConfigUpdated = "config.updated" // 配置更改 + ConfigUpdated = "config.updated" // 配置更改。当服务器第一次启动载入配置时也会触发此事件(由 ServerInitializeDone 事件联动),其中 old 全部为go类型零值, new 为载入的配置内容,若在配置载入前读取Conf,返回的将是零值配置。当出现错误时,配置修改将会被撤回。 - ProcessStart = "process.start" // 进程启动 + ProcessStart = "process.start" // 进程启动,如果在此事件的监听器中返回错误,进程将退出。注意:此时数据库、配置进行初始化,不应该在此事件的监听器中执行需要数据库或配置的操作,即使需要进行操作数据库和配置,请将优先级设置为event.Max以下 ProcessExit = "process.exit" // 进程停止 - ServerInitializeStart = "server.routers.start" // 服务器路由初始化开始 + ServerInitializeStart = "server.routers.start" // 服务器路由初始化开始,如果在此事件的监听器中返回错误,进程将退出 ServerInitializeDone = "server.routers.done" // 服务器路由初始化完成 ServerListenGrpcStart = "server.listen.grpc.start" // 服务器开始监听 gRPC ServerListenGrpcStop = "server.listen.grpc.stop" // 服务器停止监听 gRPC @@ -41,7 +41,7 @@ const ( SchedulerEvery5Minutes = "scheduler.every5minutes" // 每五分钟定时触发 SchedulerEvery30Minutes = "scheduler.every30minutes" // 每三十分钟定时触发 SchedulerEveryHour = "scheduler.everyhour" // 每小时定时触发 - SchedulerEveryDay = "scheduler.everyday" // 每天定时触发 + SchedulerEveryDay = "scheduler.everyday" // 每天定时触发,服务器启动的当天不会触发此事件 NotificationSent = "notification.sent" // 通知发送 NotificationFailed = "notification.failed" // 通知发送失败 diff --git a/internal/geoip/geoip.go b/internal/geoip/geoip.go index 4cd6478..5f4f00e 100644 --- a/internal/geoip/geoip.go +++ b/internal/geoip/geoip.go @@ -7,7 +7,9 @@ import ( "time" "unicode" + "github.com/gookit/event" "github.com/komari-monitor/komari/internal/conf" + "github.com/komari-monitor/komari/internal/eventType" "github.com/patrickmn/go-cache" ) @@ -22,44 +24,31 @@ type GeoInfo struct { func init() { CurrentProvider = &EmptyProvider{} geoCache = cache.New(48*time.Hour, 1*time.Hour) -} -// GeoIPService 接口定义了获取地理位置信息的核心方法。 -// 任何实现此接口的类型都可以作为地理位置服务提供者。 -type GeoIPService interface { - Name() string - - GetGeoInfo(ip net.IP) (*GeoInfo, error) - - UpdateDatabase() error - - Close() error -} - -func GetRegionUnicodeEmoji(isoCode string) string { - if len(isoCode) != 2 { - return "" - } - isoCode = strings.ToUpper(isoCode) - - if !unicode.IsLetter(rune(isoCode[0])) || !unicode.IsLetter(rune(isoCode[1])) { - return "" - } - - rune1 := rune(0x1F1E6 + (rune(isoCode[0]) - 'A')) - rune2 := rune(0x1F1E6 + (rune(isoCode[1]) - 'A')) - return string(rune1) + string(rune2) -} - -func InitGeoIp() { - config, err := conf.GetWithV1Format() + err := SetProvider("empty") if err != nil { - panic("Failed to get configuration for GeoIP: " + err.Error()) + log.Printf("Failed to set initial GeoIP provider: %v", err) } - if !config.GeoIpEnabled { - return - } - switch config.GeoIpProvider { + + event.On(eventType.ConfigUpdated, event.ListenerFunc(func(e event.Event) error { + oldConf, newConf, err := conf.FromEvent(e) + if oldConf.GeoIp.GeoIpProvider == newConf.GeoIp.GeoIpProvider { + return nil + } + if err != nil { + log.Printf("Failed to parse config from event: %v", err) + return err + } + err = SetProvider(newConf.GeoIp.GeoIpProvider) + if err != nil { + log.Printf("Failed to set GeoIP provider: %v", err) + } + return nil + })) +} + +func SetProvider(provider string) error { + switch provider { case "mmdb": NewCurrentProvider, err := NewMaxMindGeoIPService() if err != nil { @@ -110,6 +99,34 @@ func InitGeoIp() { default: CurrentProvider = &EmptyProvider{} } + return nil +} + +// GeoIPService 接口定义了获取地理位置信息的核心方法。 +// 任何实现此接口的类型都可以作为地理位置服务提供者。 +type GeoIPService interface { + Name() string + + GetGeoInfo(ip net.IP) (*GeoInfo, error) + + UpdateDatabase() error + + Close() error +} + +func GetRegionUnicodeEmoji(isoCode string) string { + if len(isoCode) != 2 { + return "" + } + isoCode = strings.ToUpper(isoCode) + + if !unicode.IsLetter(rune(isoCode[0])) || !unicode.IsLetter(rune(isoCode[1])) { + return "" + } + + rune1 := rune(0x1F1E6 + (rune(isoCode[0]) - 'A')) + rune2 := rune(0x1F1E6 + (rune(isoCode[1]) - 'A')) + return string(rune1) + string(rune2) } func GetGeoInfo(ip net.IP) (*GeoInfo, error) { diff --git a/internal/messageSender/init.go b/internal/messageSender/init.go new file mode 100644 index 0000000..4ea4dfa --- /dev/null +++ b/internal/messageSender/init.go @@ -0,0 +1,23 @@ +package messageSender + +import ( + "log" + + "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/conf" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + event.On(eventType.ConfigUpdated, event.ListenerFunc(func(e event.Event) error { + oldConf, newConf, err := conf.FromEvent(e) + if err != nil { + log.Printf("Failed to parse config from event: %v", err) + return err + } + if newConf.Notification.NotificationMethod != oldConf.Notification.NotificationMethod { + Initialize() + } + return nil + }), event.Max) +} diff --git a/server/grpc.go b/internal/nezha/grpc.go similarity index 94% rename from server/grpc.go rename to internal/nezha/grpc.go index 9377807..3e78923 100644 --- a/server/grpc.go +++ b/internal/nezha/grpc.go @@ -1,4 +1,4 @@ -package server +package nezha import ( "fmt" @@ -33,35 +33,25 @@ import ( "gorm.io/gorm/clause" ) -func StartNezhaGRPCServer(listen string) { +func init() { event.On(eventType.ConfigUpdated, event.ListenerFunc(func(e event.Event) error { - New := e.Get("new").(conf.Config) - Old := e.Get("old").(conf.Config) - if New.Compact.Nezha.NezhaCompatEnabled != Old.Compact.Nezha.NezhaCompatEnabled { + Old, New, _ := conf.FromEvent(e) + if New.Compact.Nezha.NezhaCompatEnabled != Old.Compact.Nezha.NezhaCompatEnabled || New.Compact.Nezha.NezhaCompatListen != Old.Compact.Nezha.NezhaCompatListen { if New.Compact.Nezha.NezhaCompatEnabled { + StopNezhaCompat() if err := StartNezhaCompat(New.Compact.Nezha.NezhaCompatListen); err != nil { - log.Printf("start Nezha compat server error: %v", err) - auditlog.EventLog("error", fmt.Sprintf("start Nezha compat server error: %v", err)) + return fmt.Errorf("start Nezha compat server error: %w", err) } event.Trigger(eventType.ServerListenGrpcStart, nil) } else { if err := StopNezhaCompat(); err != nil { - log.Printf("stop Nezha compat server error: %v", err) - auditlog.EventLog("error", fmt.Sprintf("stop Nezha compat server error: %v", err)) + return fmt.Errorf("stop Nezha compat server error: %w", err) } event.Trigger(eventType.ServerListenGrpcStop, nil) } } return nil })) - - if listen == "" { - return - } - if err := StartNezhaCompat(listen); err != nil { - log.Printf("Nezha compat server error: %v", err) - auditlog.EventLog("error", fmt.Sprintf("Nezha compat server error: %v", err)) - } } // ---- Manual start/stop support ---- @@ -131,7 +121,7 @@ func StopNezhaCompat() error { nezhaOnceM.Lock() defer nezhaOnceM.Unlock() if nezhaSrv == nil { - return errors.New("nezha compat server not running") + return nil } // 强制立即断开所有连接与流,不等待在途 RPC 完成。 nezhaSrv.Stop() diff --git a/internal/notifier/init.go b/internal/notifier/init.go new file mode 100644 index 0000000..ffdda45 --- /dev/null +++ b/internal/notifier/init.go @@ -0,0 +1,18 @@ +package notifier + +import ( + "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + event.On(eventType.SchedulerEveryMinute, event.ListenerFunc(func(e event.Event) error { + CheckTraffic() + return nil + })) + event.On(eventType.ServerInitializeDone, event.ListenerFunc(func(e event.Event) error { + go CheckExpireScheduledWork() + ReloadLoadNotification() + return nil + })) +} diff --git a/internal/notifier/load.go b/internal/notifier/load.go index e3776bf..f89afe7 100644 --- a/internal/notifier/load.go +++ b/internal/notifier/load.go @@ -218,7 +218,11 @@ func updateLastNotified(taskId uint, notifyTime time.Time) { } } -// ReloadLoadNotificationSchedule 加载或重载时间表 -func ReloadLoadNotificationSchedule(loadNotifications []models.LoadNotification) error { +func ReloadLoadNotification() error { + db := dbcore.GetDBInstance() + var loadNotifications []models.LoadNotification + if err := db.Find(&loadNotifications).Error; err != nil { + return err + } return LoadNotificationManager.Reload(loadNotifications) } diff --git a/internal/oauth/oauth.go b/internal/oauth/oauth.go index b51de88..d1e1291 100644 --- a/internal/oauth/oauth.go +++ b/internal/oauth/oauth.go @@ -6,16 +6,18 @@ import ( "log" "sync" + "github.com/gookit/event" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database" + "github.com/komari-monitor/komari/internal/database/auditlog" "github.com/komari-monitor/komari/internal/database/models" + "github.com/komari-monitor/komari/internal/eventType" "github.com/komari-monitor/komari/internal/oauth/factory" ) var ( currentProvider factory.IOidcProvider mu = sync.Mutex{} - once = sync.Once{} ) func CurrentProvider() factory.IOidcProvider { @@ -47,8 +49,8 @@ func LoadProvider(name string, configJson string) error { return nil } -func Initialize() error { - once.Do(func() { +func init() { + event.On(eventType.ServerInitializeStart, event.ListenerFunc(func(e event.Event) error { all := factory.GetAllOidcProviders() for _, provider := range all { if _, err := database.GetOidcConfigByName(provider.GetName()); err == nil { @@ -58,33 +60,36 @@ func Initialize() error { config := provider.GetConfiguration() configBytes, err := json.Marshal(config) if err != nil { - log.Printf("Failed to marshal config for provider %s: %v", provider.GetName(), err) - return + return fmt.Errorf("failed to marshal config for provider %s: %v", provider.GetName(), err) } if err := database.SaveOidcConfig(&models.OidcProvider{ Name: provider.GetName(), Addition: string(configBytes), }); err != nil { - log.Printf("Failed to save default config for provider %s: %v", provider.GetName(), err) - return + return fmt.Errorf("failed to save default config for provider %s: %v", provider.GetName(), err) } } - }) - cfg, _ := conf.GetWithV1Format() - if cfg.OAuthProvider == "" || cfg.OAuthProvider == "none" { - LoadProvider("empty", "{}") return nil - } - provider, err := database.GetOidcConfigByName(cfg.OAuthProvider) - if err != nil { - // 如果没有找到配置,使用empty provider - LoadProvider("empty", "{}") + })) + + event.On(eventType.ConfigUpdated, event.ListenerFunc(func(e event.Event) error { + oldConf, newConf, err := conf.FromEvent(e) + if err != nil { + log.Printf("Failed to parse config from event: %v", err) + } + + if newConf.Login.OAuthProvider != oldConf.Login.OAuthProvider { + oidcProvider, err := database.GetOidcConfigByName(newConf.Login.OAuthProvider) + if err != nil { + log.Printf("Failed to get OIDC provider config: %v", err) + } else { + log.Printf("Using %s as OIDC provider", oidcProvider.Name) + } + err = LoadProvider(oidcProvider.Name, oidcProvider.Addition) + if err != nil { + auditlog.EventLog("error", fmt.Sprintf("Failed to load OIDC provider: %v", err)) + } + } return nil - } - err = LoadProvider(provider.Name, provider.Addition) - if err != nil { - log.Printf("Failed to load OIDC provider %s: %v", provider.Name, err) - return err - } - return nil + })) } diff --git a/internal/patch/patch.go b/internal/patch/patch.go index dca66a7..e583f5e 100644 --- a/internal/patch/patch.go +++ b/internal/patch/patch.go @@ -3,36 +3,41 @@ package patch import ( "log" + "github.com/gookit/event" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" + "github.com/komari-monitor/komari/internal/eventType" ) -func ApplyPatch() { - db := dbcore.GetDBInstance() - // 0.0.5 迁移ClientInfo - if db.Migrator().HasTable("client_infos") { - v0_0_5(db) - } - // 0.0.5a 修正cors拼写错误 - if db.Migrator().HasColumn(&conf.V1Struct{}, "allow_cros") { - v0_0_5a(db) - } - // 0.1.4 重建LoadNotification表 - if db.Migrator().HasColumn(&models.LoadNotification{}, "client") { - log.Println("[>0.1.4] Rebuilding LoadNotification table....") - db.Migrator().DropTable(&models.LoadNotification{}) - } - // 1.0.2 合并OIDC提供商表 - if !db.Migrator().HasTable(&models.OidcProvider{}) && db.Migrator().HasTable(&conf.V1Struct{}) { - v1_0_2_Oidc(db) - } - // 1.0.2 迁移消息发送配置到单独的表 - if !db.Migrator().HasTable(&models.MessageSenderProvider{}) && db.Migrator().HasTable(&conf.V1Struct{}) { - v1_0_2_MessageSender(db) - } - // 1.1.4 迁移配置表 - if db.Migrator().HasColumn(&conf.V1Struct{}, "id") { - v1_1_4(db) - } +func init() { + event.On(eventType.ProcessStart, event.ListenerFunc(func(e event.Event) error { + db := dbcore.GetDBInstance() + // 0.0.5 迁移ClientInfo + if db.Migrator().HasTable("client_infos") { + v0_0_5(db) + } + // 0.0.5a 修正cors拼写错误 + if db.Migrator().HasColumn(&conf.V1Struct{}, "allow_cros") { + v0_0_5a(db) + } + // 0.1.4 重建LoadNotification表 + if db.Migrator().HasColumn(&models.LoadNotification{}, "client") { + log.Println("[>0.1.4] Rebuilding LoadNotification table....") + db.Migrator().DropTable(&models.LoadNotification{}) + } + // 1.0.2 合并OIDC提供商表 + if !db.Migrator().HasTable(&models.OidcProvider{}) && db.Migrator().HasTable(&conf.V1Struct{}) { + v1_0_2_Oidc(db) + } + // 1.0.2 迁移消息发送配置到单独的表 + if !db.Migrator().HasTable(&models.MessageSenderProvider{}) && db.Migrator().HasTable(&conf.V1Struct{}) { + v1_0_2_MessageSender(db) + } + // 1.1.4 迁移配置表 + if db.Migrator().HasColumn(&conf.V1Struct{}, "id") { + v1_1_4(db) + } + return nil + }), event.Max) } diff --git a/internal/pingSchedule/init.go b/internal/pingSchedule/init.go new file mode 100644 index 0000000..6e7c694 --- /dev/null +++ b/internal/pingSchedule/init.go @@ -0,0 +1,12 @@ +package pingSchedule + +import ( + "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + event.On(eventType.ServerInitializeDone, event.ListenerFunc(func(e event.Event) error { + return ReloadPingSchedule() + })) +} diff --git a/internal/pingSchedule/pingSchedule.go b/internal/pingSchedule/pingSchedule.go index ddc0699..294aa0a 100644 --- a/internal/pingSchedule/pingSchedule.go +++ b/internal/pingSchedule/pingSchedule.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/komari-monitor/komari/internal/database/dbcore" "github.com/komari-monitor/komari/internal/database/models" "github.com/komari-monitor/komari/internal/ws" ) @@ -107,7 +108,16 @@ func executePingTask(ctx context.Context, task models.PingTask, onlineClients ma } } -// ReloadPingSchedule 加载或重载时间表 -func ReloadPingSchedule(pingTasks []models.PingTask) error { +// ReloadPingScheduleWithTasks 加载或重载时间表 +func ReloadPingScheduleWithTasks(pingTasks []models.PingTask) error { + return manager.Reload(pingTasks) +} + +func ReloadPingSchedule() error { + db := dbcore.GetDBInstance() + var pingTasks []models.PingTask + if err := db.Find(&pingTasks).Error; err != nil { + return err + } return manager.Reload(pingTasks) } diff --git a/internal/restore/init.go b/internal/restore/init.go new file mode 100644 index 0000000..553910e --- /dev/null +++ b/internal/restore/init.go @@ -0,0 +1,15 @@ +package restore + +import ( + "github.com/gookit/event" + "github.com/komari-monitor/komari/internal/eventType" +) + +func init() { + event.On(eventType.ProcessStart, event.ListenerFunc(func(e event.Event) error { + if NeedBackupRestore() { + RestoreBackup() + } + return nil + })) +} diff --git a/server/routers.go b/server/routers.go index 8928c6b..a221d1b 100644 --- a/server/routers.go +++ b/server/routers.go @@ -1,17 +1,10 @@ package server import ( - "log/slog" - "time" - "github.com/gin-gonic/gin" "github.com/gookit/event" - "github.com/komari-monitor/komari/internal" - "github.com/komari-monitor/komari/internal/api_rpc" "github.com/komari-monitor/komari/internal/conf" "github.com/komari-monitor/komari/internal/eventType" - "github.com/komari-monitor/komari/internal/geoip" - "github.com/komari-monitor/komari/internal/messageSender" "github.com/komari-monitor/komari/public" ) @@ -20,20 +13,11 @@ var ( ) func Init(r *gin.Engine) { - config, _ := conf.GetWithV1Format() - AllowCors = config.AllowCors event.On(eventType.ConfigUpdated, event.ListenerFunc(func(e event.Event) error { newConf := e.Get("new").(conf.Config) - oldConf := e.Get("old").(conf.Config) AllowCors = newConf.Site.AllowCors public.UpdateIndex(newConf.ToV1Format()) - if newConf.GeoIp.GeoIpProvider != oldConf.GeoIp.GeoIpProvider { - go geoip.InitGeoIp() - } - if newConf.Notification.NotificationMethod != oldConf.Notification.NotificationMethod { - go messageSender.Initialize() - } return nil }), event.High) @@ -56,37 +40,4 @@ func Init(r *gin.Engine) { public.Static(r.Group("/"), func(handlers ...gin.HandlerFunc) { r.NoRoute(handlers...) }) - // #region 静态文件服务 - public.UpdateIndex(config) - - internal.LoadApiV1Routes(r, config) - - api_rpc.RegisterRouters("/api/rpc2", r) -} - -func ScheduledTasksInit() { - every1m := time.NewTicker(1 * time.Minute) - every5m := time.NewTicker(5 * time.Minute) - every30m := time.NewTicker(30 * time.Minute) - every1h := time.NewTicker(1 * time.Hour) - every1d := time.NewTicker(24 * time.Hour) - for { - var err error = nil - var e event.Event - select { - case <-every1m.C: - err, e = event.Trigger(eventType.SchedulerEveryMinute, event.M{"interval": "1m"}) - case <-every5m.C: - err, e = event.Trigger(eventType.SchedulerEvery5Minutes, event.M{"interval": "5m"}) - case <-every30m.C: - err, e = event.Trigger(eventType.SchedulerEvery30Minutes, event.M{"interval": "30m"}) - case <-every1h.C: - err, e = event.Trigger(eventType.SchedulerEveryHour, event.M{"interval": "1h"}) - case <-every1d.C: - err, e = event.Trigger(eventType.SchedulerEveryDay, event.M{"interval": "1d"}) - } - if err != nil { - slog.Warn("Scheduled task error:", "error", err, "event", e) - } - } }