feat: add notify icon read

This commit is contained in:
engigu
2026-03-09 18:10:23 +08:00
parent e7da316c90
commit 77cf12c9b6
46 changed files with 1347 additions and 252 deletions
+108
View File
@@ -0,0 +1,108 @@
package controllers
import (
"github.com/engigu/baihu-panel/internal/services"
"github.com/engigu/baihu-panel/internal/utils"
"github.com/gin-gonic/gin"
)
type AppLogController struct {
appLogService *services.AppLogService
}
func NewAppLogController() *AppLogController {
return &AppLogController{
appLogService: services.NewAppLogService(),
}
}
// GetLogs 获取应用日志列表
// @Summary 获取应用日志列表
// @Tags 应用日志
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param category query string false "日志分类"
// @Param status query string false "状态"
// @Param level query string false "级别"
// @Param keyword query string false "搜索关键词"
// @Param page query int false "页码"
// @Param page_size query int false "每页数量"
// @Success 200 {object} utils.Response
// @Router /app-logs [get]
func (ac *AppLogController) GetLogs(c *gin.Context) {
p := utils.ParsePagination(c)
category := c.Query("category")
status := c.Query("status")
level := c.Query("level")
keyword := c.Query("keyword")
logs, total, err := ac.appLogService.List(category, status, level, p.Page, p.PageSize, keyword)
if err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.PaginatedResponse(c, logs, total, p)
}
// MarkAsRead 标记已读
// @Summary 标记已读
// @Tags 应用日志
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body object true "请求体"
// @Success 200 {object} utils.Response
// @Router /app-logs/read [post]
func (ac *AppLogController) MarkAsRead(c *gin.Context) {
var req struct {
ID string `json:"id"`
Category string `json:"category"`
}
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, err.Error())
return
}
if req.ID != "" {
if err := ac.appLogService.MarkAsRead(req.ID); err != nil {
utils.BadRequest(c, err.Error())
return
}
} else if req.Category != "" {
if err := ac.appLogService.MarkAllAsRead(req.Category); err != nil {
utils.BadRequest(c, err.Error())
return
}
} else {
utils.BadRequest(c, "id 或 category 必须提供")
return
}
utils.SuccessMsg(c, "标记成功")
}
// ClearLogs 清理日志
// @Summary 清理日志
// @Tags 应用日志
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body object true "请求体"
// @Success 200 {object} utils.Response
// @Router /app-logs/clear [post]
func (ac *AppLogController) ClearLogs(c *gin.Context) {
var req struct {
Category string `json:"category"`
}
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, err.Error())
return
}
if err := ac.appLogService.Clear(req.Category); err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.SuccessMsg(c, "清理成功")
}
+37 -11
View File
@@ -7,6 +7,7 @@ import (
"time"
"github.com/engigu/baihu-panel/internal/constant"
"github.com/engigu/baihu-panel/internal/eventbus"
"github.com/engigu/baihu-panel/internal/middleware"
"github.com/engigu/baihu-panel/internal/models/vo"
"github.com/engigu/baihu-panel/internal/services"
@@ -54,10 +55,13 @@ func (ac *AuthController) Login(c *gin.Context) {
if val, ok := loginAttempts.Load(ip); ok {
attempt := val.(*loginAttempt)
if attempt.Count >= 5 && time.Since(attempt.LastAttempt) < time.Minute {
ac.loginLogService.Create(req.Username, ip, userAgent, "failed", "尝试次数过多,请一分钟后再试")
go services.NewNotificationService().TriggerEvent(constant.BindingTypeSystem, constant.EventBruteForceLogin, "", map[string]interface{}{
"ip": ip,
"username": req.Username,
eventbus.DefaultBus.Publish(eventbus.Event{
Type: constant.EventBruteForceLogin,
Payload: map[string]interface{}{
"ip": ip,
"username": req.Username,
"userAgent": userAgent,
},
})
utils.TooManyRequests(c, "尝试次数过多,请一分钟后再试")
return
@@ -77,7 +81,16 @@ func (ac *AuthController) Login(c *gin.Context) {
attempt.LastAttempt = time.Now()
// 记录登录失败日志
ac.loginLogService.Create(req.Username, ip, userAgent, "failed", "用户名或密码错误")
eventbus.DefaultBus.Publish(eventbus.Event{
Type: constant.EventUserLogin,
Payload: map[string]interface{}{
"ip": ip,
"username": req.Username,
"userAgent": userAgent,
"status": "failed",
"message": "用户名或密码错误",
},
})
utils.Unauthorized(c, "用户名或密码错误")
return
}
@@ -96,7 +109,16 @@ func (ac *AuthController) Login(c *gin.Context) {
// 生成 token
token, err := utils.GenerateToken(user.ID, user.Username, expireDays, constant.Secret)
if err != nil {
ac.loginLogService.Create(req.Username, ip, userAgent, "failed", "Token生成失败")
eventbus.DefaultBus.Publish(eventbus.Event{
Type: constant.EventUserLogin,
Payload: map[string]interface{}{
"ip": ip,
"username": req.Username,
"userAgent": userAgent,
"status": "failed",
"message": "Token生成失败",
},
})
utils.ServerError(c, "登录失败")
return
}
@@ -105,11 +127,15 @@ func (ac *AuthController) Login(c *gin.Context) {
middleware.SetAuthCookie(c, token, expireDays)
// 记录登录成功日志
ac.loginLogService.Create(req.Username, ip, userAgent, "success", "登录成功")
go services.NewNotificationService().TriggerEvent(constant.BindingTypeSystem, constant.EventUserLogin, "", map[string]interface{}{
"ip": ip,
"username": req.Username,
eventbus.DefaultBus.Publish(eventbus.Event{
Type: constant.EventUserLogin,
Payload: map[string]interface{}{
"ip": ip,
"username": req.Username,
"userAgent": userAgent,
"status": "success",
"message": "登录成功",
},
})
utils.Success(c, gin.H{
+1 -1
View File
@@ -87,7 +87,7 @@ func (lc *LogController) GetLogs(c *gin.Context) {
TaskName: task.Name,
TaskType: taskType,
AgentID: log.AgentID,
Command: log.Command,
Command: string(log.Command),
Status: log.Status,
Duration: log.Duration,
StartTime: log.StartTime,
+2 -2
View File
@@ -37,7 +37,7 @@ func (lc *LogWSController) StreamLog(c *gin.Context) {
if err := database.DB.Where("id = ?", logID).First(&taskLog).Error; err == nil {
if taskLog.Status != "running" {
// 已结束,读取库内日志
content, err := utils.DecompressFromBase64(taskLog.Output)
content, err := utils.DecompressFromBase64(string(taskLog.Output))
if err != nil {
conn.WriteMessage(websocket.TextMessage, []byte("解压日志失败: "+err.Error()))
return
@@ -75,7 +75,7 @@ func (lc *LogWSController) StreamLog(c *gin.Context) {
// 任务结束,尝试刷新最后一次库内完整内容
var finalLog models.TaskLog
if err := database.DB.Where("id = ?", logID).First(&finalLog).Error; err == nil {
content, _ := utils.DecompressFromBase64(finalLog.Output)
content, _ := utils.DecompressFromBase64(string(finalLog.Output))
if content != "" {
conn.WriteMessage(websocket.TextMessage, []byte("\n--- 任务已结束 ---\n"))
// 这里可以选择性再推一次完整版,或直接退出
+6 -3
View File
@@ -10,9 +10,9 @@ import (
"os"
"strings"
"time"
"github.com/engigu/baihu-panel/internal/constant"
"github.com/engigu/baihu-panel/internal/database"
"github.com/engigu/baihu-panel/internal/eventbus"
"github.com/engigu/baihu-panel/internal/models"
"github.com/engigu/baihu-panel/internal/models/vo"
"github.com/engigu/baihu-panel/internal/services"
@@ -76,8 +76,11 @@ func (sc *SettingsController) ChangePassword(c *gin.Context) {
return
}
go services.NewNotificationService().TriggerEvent(constant.BindingTypeSystem, constant.EventPasswordChanged, "", map[string]interface{}{
"username": user.Username,
eventbus.DefaultBus.Publish(eventbus.Event{
Type: constant.EventPasswordChanged,
Payload: map[string]interface{}{
"username": user.Username,
},
})
utils.SuccessMsg(c, "密码修改成功")