Merge pull request #378 from komari-monitor/dev

refactor: 事件路由
This commit is contained in:
Akizon
2025-12-02 02:12:24 +08:00
committed by GitHub
64 changed files with 893 additions and 676 deletions
+7 -2
View File
@@ -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)
+77 -120
View File
@@ -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"})
}
}
}()
}
+27
View File
@@ -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() {}
+2 -2
View File
@@ -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: [] }
+15
View File
@@ -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
}))
}
+3 -2
View File
@@ -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
}
+4 -54
View File
@@ -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 {
+4 -3
View File
@@ -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
}
+8 -8
View File
@@ -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, "")
}
+4 -4
View File
@@ -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)
}
+20 -20
View File
@@ -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)
}
+9 -9
View File
@@ -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
}
}
+8 -8
View File
@@ -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,
})
+6 -6
View File
@@ -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})
}
+11 -11
View File
@@ -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"})
}
+14 -14
View File
@@ -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)
}
+15 -15
View File
@@ -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(&notifications); 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(&notifications).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)
}
+15 -15
View File
@@ -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"})
}
+12 -12
View File
@@ -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)
}
+7 -7
View File
@@ -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)
}
+7 -7
View File
@@ -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)
}
+25 -25
View File
@@ -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)
}
+7 -7
View File
@@ -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)
}
+36 -36
View File
@@ -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)
}
+8 -8
View File
@@ -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)
}
+3 -3
View File
@@ -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)
}
+7 -7
View File
@@ -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})
}
+12 -12
View File
@@ -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()
+6 -6
View File
@@ -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})
}
+3 -3
View File
@@ -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
}
+7 -7
View File
@@ -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)
}
@@ -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
+6 -4
View File
@@ -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)
}
+10 -9
View File
@@ -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",
+3 -2
View File
@@ -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)
}
+3 -2
View File
@@ -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)
}
+13 -13
View File
@@ -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)
}
+40
View File
@@ -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,
})
}
+3 -3
View File
@@ -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)
}
@@ -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")
+11
View File
@@ -0,0 +1,11 @@
package vars
import (
"time"
"github.com/patrickmn/go-cache"
)
var (
Records = cache.New(1*time.Minute, 1*time.Minute)
)
@@ -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) {
+28
View File
@@ -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
}))
}
+35 -18
View File
@@ -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
}
+34
View File
@@ -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)
}
+24
View File
@@ -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
}))
}
+9
View File
@@ -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()
+9
View File
@@ -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
}))
}
+13
View File
@@ -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)
}
+1 -6
View File
@@ -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()
}
+2 -6
View File
@@ -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) {
+4 -4
View File
@@ -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" // 通知发送失败
+52 -35
View File
@@ -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) {
+23
View File
@@ -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)
}
+8 -18
View File
@@ -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()
+18
View File
@@ -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
}))
}
+6 -2
View File
@@ -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)
}
+28 -23
View File
@@ -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
}))
}
+32 -27
View File
@@ -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)
}
+12
View File
@@ -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()
}))
}
+12 -2
View File
@@ -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)
}
+15
View File
@@ -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
}))
}
-49
View File
@@ -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)
}
}
}