From 77ceef7616c2446a7d12c7afe27bd6422857b8f6 Mon Sep 17 00:00:00 2001 From: engigu Date: Mon, 9 Mar 2026 21:31:54 +0800 Subject: [PATCH] feat: opt message logs page --- internal/constant/constant.go | 1 + internal/controllers/settings_controller.go | 41 ++- internal/database/migrate.go | 1 - internal/models/login_log.go | 20 -- internal/models/vo/sys_vo.go | 37 --- internal/services/app_log_service.go | 61 ++-- internal/services/backup_service.go | 7 +- internal/services/login_log_service.go | 33 +- internal/services/migration_v3.go | 1 - web/src/components/SystemNotice.vue | 17 +- web/src/layouts/MainLayout.vue | 2 +- web/src/router/index.ts | 2 +- web/src/views/loginlogs/LoginLogs.vue | 212 ++----------- web/src/views/loginlogs/tabs/LoginLogTab.vue | 194 ++++++++++++ .../views/loginlogs/tabs/SystemEventTab.vue | 284 ++++++++++++++++++ web/src/views/notify/Notify.vue | 15 +- web/src/views/notify/components/PushLog.vue | 32 +- 17 files changed, 630 insertions(+), 330 deletions(-) delete mode 100644 internal/models/login_log.go create mode 100644 web/src/views/loginlogs/tabs/LoginLogTab.vue create mode 100644 web/src/views/loginlogs/tabs/SystemEventTab.vue diff --git a/internal/constant/constant.go b/internal/constant/constant.go index 63fedc3..1fabd39 100644 --- a/internal/constant/constant.go +++ b/internal/constant/constant.go @@ -119,6 +119,7 @@ const ( LogCategoryDefault = "default" LogCategorySystemNotice = "system_notice" LogCategoryPushLog = "push_log" + LogCategoryLoginLog = "login_log" // AppLog 级别 LogLevelInfo = "info" diff --git a/internal/controllers/settings_controller.go b/internal/controllers/settings_controller.go index 33d25ab..72ddf9f 100644 --- a/internal/controllers/settings_controller.go +++ b/internal/controllers/settings_controller.go @@ -10,6 +10,7 @@ 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" @@ -143,18 +144,18 @@ func (sc *SettingsController) GetPublicSiteSettings(c *gin.Context) { // UpdateSiteSettings 更新站点设置 func (sc *SettingsController) UpdateSiteSettings(c *gin.Context) { var req struct { - Title string `json:"title"` - Subtitle string `json:"subtitle"` - Icon string `json:"icon"` - PageSize string `json:"page_size"` - CookieDays string `json:"cookie_days"` - OpenapiEnabled bool `json:"openapi_enabled"` - OpenapiToken string `json:"openapi_token"` - OpenapiTokenExpire string `json:"openapi_token_expire"` - SystemNoticeDays int `json:"system_notice_days"` - SystemNoticeMaxCount int `json:"system_notice_max_count"` - PushLogDays int `json:"push_log_days"` - PushLogMaxCount int `json:"push_log_max_count"` + Title string `json:"title"` + Subtitle string `json:"subtitle"` + Icon string `json:"icon"` + PageSize string `json:"page_size"` + CookieDays string `json:"cookie_days"` + OpenapiEnabled bool `json:"openapi_enabled"` + OpenapiToken string `json:"openapi_token"` + OpenapiTokenExpire string `json:"openapi_token_expire"` + SystemNoticeDays int `json:"system_notice_days"` + SystemNoticeMaxCount int `json:"system_notice_max_count"` + PushLogDays int `json:"push_log_days"` + PushLogMaxCount int `json:"push_log_max_count"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -341,8 +342,22 @@ func (sc *SettingsController) GetLoginLogs(c *gin.Context) { return } + // 将 AppLog 转换为 LoginLogVO 返回,保持前端兼容性 + vos := make([]*vo.LoginLogVO, len(logs)) + for i, log := range logs { + vos[i] = &vo.LoginLogVO{ + ID: log.ID, + Username: log.Title, + IP: log.RefID, + UserAgent: string(log.Content), + Status: log.Status, + Message: string(log.ErrorMsg), + CreatedAt: log.CreatedAt, + } + } + utils.Success(c, utils.PaginationData{ - Data: vo.ToLoginLogVOListFromModels(logs), + Data: vos, Total: total, Page: page, PageSize: pageSize, diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 5d610b7..9fd5c16 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -21,7 +21,6 @@ func Migrate() error { &models.Script{}, &models.EnvironmentVariable{}, &models.Setting{}, - &models.LoginLog{}, &models.SendStats{}, &models.Dependency{}, &models.Agent{}, diff --git a/internal/models/login_log.go b/internal/models/login_log.go deleted file mode 100644 index ac89320..0000000 --- a/internal/models/login_log.go +++ /dev/null @@ -1,20 +0,0 @@ -package models - -import ( - "github.com/engigu/baihu-panel/internal/constant" -) - -// LoginLog 登录日志 -type LoginLog struct { - ID string `json:"id" gorm:"primaryKey;size:20"` - Username string `json:"username" gorm:"size:100;index;not null"` - IP string `json:"ip" gorm:"size:50"` - UserAgent string `json:"user_agent" gorm:"size:500"` - Status string `json:"status" gorm:"size:20;index"` // success, failed - Message string `json:"message" gorm:"size:255"` - CreatedAt LocalTime `json:"created_at" gorm:"index"` -} - -func (LoginLog) TableName() string { - return constant.TablePrefix + "login_logs" -} diff --git a/internal/models/vo/sys_vo.go b/internal/models/vo/sys_vo.go index 53a1de0..01c61b1 100644 --- a/internal/models/vo/sys_vo.go +++ b/internal/models/vo/sys_vo.go @@ -88,43 +88,6 @@ type LoginLogVO struct { CreatedAt models.LocalTime `json:"created_at"` } -// ToLoginLogVO 将 LoginLog 模型转换为 LoginLogVO -func ToLoginLogVO(log *models.LoginLog) *LoginLogVO { - if log == nil { - return nil - } - return &LoginLogVO{ - ID: log.ID, - Username: log.Username, - IP: log.IP, - UserAgent: log.UserAgent, - Status: log.Status, - Message: log.Message, - CreatedAt: log.CreatedAt, - } -} - -// ToLoginLogVOList 将 LoginLog 模型列表转换为 LoginLogVO 列表 -func ToLoginLogVOList(logs []*models.LoginLog) []*LoginLogVO { - if logs == nil { - return nil - } - vos := make([]*LoginLogVO, len(logs)) - for i, l := range logs { - vos[i] = ToLoginLogVO(l) - } - return vos -} - -// ToLoginLogVOListFromModels 将 LoginLog 模型列表转换为 LoginLogVO 列表 -func ToLoginLogVOListFromModels(logs []models.LoginLog) []*LoginLogVO { - vos := make([]*LoginLogVO, len(logs)) - for i := range logs { - vos[i] = ToLoginLogVO(&logs[i]) - } - return vos -} - // TokenConfig Token 配置结构体 type TokenConfig struct { Enabled bool `json:"enabled"` diff --git a/internal/services/app_log_service.go b/internal/services/app_log_service.go index 98a3da2..52b63b6 100644 --- a/internal/services/app_log_service.go +++ b/internal/services/app_log_service.go @@ -5,6 +5,7 @@ import ( "time" "fmt" + "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/eventbus" @@ -185,40 +186,42 @@ func (s *AppLogService) SubscribeEvents(bus *eventbus.EventBus) { }) // 3. 将某些业务事件转化为系统内部通知 (自动出现在小铃铛) - bus.Subscribe(constant.EventTaskFailed, func(e eventbus.Event) { - payload, ok := e.Payload.(map[string]interface{}) - if !ok { - return - } - taskName, _ := payload["task_name"].(string) - errMsg, _ := payload["error"].(string) + /* + bus.Subscribe(constant.EventTaskFailed, func(e eventbus.Event) { + payload, ok := e.Payload.(map[string]interface{}) + if !ok { + return + } + taskName, _ := payload["task_name"].(string) + errMsg, _ := payload["error"].(string) - bus.Publish(eventbus.Event{ - Type: constant.EventSystemNotice, - Payload: map[string]interface{}{ - "title": fmt.Sprintf("任务 [%s] 执行失败", taskName), - "content": fmt.Sprintf("错误详情: %s", errMsg), - "level": constant.LogLevelError, - }, + bus.Publish(eventbus.Event{ + Type: constant.EventSystemNotice, + Payload: map[string]interface{}{ + "title": fmt.Sprintf("任务 [%s] 执行失败", taskName), + "content": fmt.Sprintf("错误详情: %s", errMsg), + "level": constant.LogLevelError, + }, + }) }) - }) - bus.Subscribe(constant.EventTaskTimeout, func(e eventbus.Event) { - payload, ok := e.Payload.(map[string]interface{}) - if !ok { - return - } - taskName, _ := payload["task_name"].(string) + bus.Subscribe(constant.EventTaskTimeout, func(e eventbus.Event) { + payload, ok := e.Payload.(map[string]interface{}) + if !ok { + return + } + taskName, _ := payload["task_name"].(string) - bus.Publish(eventbus.Event{ - Type: constant.EventSystemNotice, - Payload: map[string]interface{}{ - "title": fmt.Sprintf("任务 [%s] 执行超时", taskName), - "content": "任务已经超过预设的运行时间并被系统强制中止。", - "level": constant.LogLevelWarning, - }, + bus.Publish(eventbus.Event{ + Type: constant.EventSystemNotice, + Payload: map[string]interface{}{ + "title": fmt.Sprintf("任务 [%s] 执行超时", taskName), + "content": "任务已经超过预设的运行时间并被系统强制中止。", + "level": constant.LogLevelWarning, + }, + }) }) - }) + */ bus.Subscribe(constant.EventPasswordChanged, func(e eventbus.Event) { payload, ok := e.Payload.(map[string]interface{}) diff --git a/internal/services/backup_service.go b/internal/services/backup_service.go index 75419a9..1ae6823 100644 --- a/internal/services/backup_service.go +++ b/internal/services/backup_service.go @@ -50,7 +50,7 @@ func (s *BackupService) getTableConfigs() []tableConfig { {"scripts.json", s.exportTable(&[]models.Script{}, true), s.restoreTable(&[]models.Script{}, true)}, {"settings.json", s.exportSettings, s.restoreSettings}, {"send_stats.json", s.exportTable(&[]models.SendStats{}, false), s.restoreTable(&[]models.SendStats{}, false)}, - {"login_logs.json", s.exportTable(&[]models.LoginLog{}, false), s.restoreTable(&[]models.LoginLog{}, false)}, + {"agents.json", s.exportTable(&[]models.Agent{}, true), s.restoreTable(&[]models.Agent{}, true)}, {"tokens.json", s.exportTable(&[]models.AgentToken{}, true), s.restoreTable(&[]models.AgentToken{}, true)}, {"languages.json", s.exportTable(&[]models.Language{}, true), s.restoreTable(&[]models.Language{}, true)}, @@ -229,7 +229,7 @@ func (s *BackupService) Restore(zipPath string) error { tx.Unscoped().Where("1=1").Delete(&models.Script{}) tx.Unscoped().Where("section != ?", BackupSection).Delete(&models.Setting{}) tx.Unscoped().Where("1=1").Delete(&models.SendStats{}) - tx.Unscoped().Where("1=1").Delete(&models.LoginLog{}) + tx.Unscoped().Where("1=1").Delete(&models.Agent{}) tx.Unscoped().Where("1=1").Delete(&models.AgentToken{}) tx.Unscoped().Where("1=1").Delete(&models.Language{}) @@ -328,8 +328,7 @@ func (s *BackupService) restoreFromZipFile(tx *gorm.DB, f *zip.File, filename st return restoreStreamBatch[models.Script](tx, decoder) case "send_stats.json": return restoreStreamBatch[models.SendStats](tx, decoder) - case "login_logs.json": - return restoreStreamBatch[models.LoginLog](tx, decoder) + case "agents.json": return restoreStreamBatch[models.Agent](tx, decoder) case "tokens.json": diff --git a/internal/services/login_log_service.go b/internal/services/login_log_service.go index 2dd6405..34e165d 100644 --- a/internal/services/login_log_service.go +++ b/internal/services/login_log_service.go @@ -2,6 +2,8 @@ package services import ( "fmt" + "time" + "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/eventbus" @@ -17,13 +19,19 @@ func NewLoginLogService() *LoginLogService { // Create 创建登录日志 func (s *LoginLogService) Create(username, ip, userAgent, status, message string) error { - log := &models.LoginLog{ - ID: utils.GenerateID(), - Username: username, - IP: ip, - UserAgent: userAgent, - Status: status, - Message: message, + level := constant.LogLevelInfo + if status != "success" { + level = constant.LogLevelWarning + } + log := &models.AppLog{ + ID: utils.GenerateID(), + Category: constant.LogCategoryLoginLog, + Title: username, + Content: models.BigText(userAgent), + Level: level, + Status: status, + RefID: ip, + ErrorMsg: models.BigText(message), } return database.DB.Create(log).Error } @@ -84,13 +92,13 @@ func (s *LoginLogService) SubscribeEvents(bus *eventbus.EventBus) { } // List 获取登录日志列表 -func (s *LoginLogService) List(page, pageSize int, username string) ([]models.LoginLog, int64, error) { - var logs []models.LoginLog +func (s *LoginLogService) List(page, pageSize int, username string) ([]models.AppLog, int64, error) { + var logs []models.AppLog var total int64 - query := database.DB.Model(&models.LoginLog{}) + query := database.DB.Model(&models.AppLog{}).Where("category = ?", constant.LogCategoryLoginLog) if username != "" { - query = query.Where("username LIKE ?", "%"+username+"%") + query = query.Where("title LIKE ?", "%"+username+"%") } if err := query.Count(&total).Error; err != nil { @@ -107,6 +115,7 @@ func (s *LoginLogService) List(page, pageSize int, username string) ([]models.Lo // CleanOldLogs 清理指定天数前的日志 func (s *LoginLogService) CleanOldLogs(days int) (int64, error) { - result := database.DB.Exec("DELETE FROM "+models.LoginLog{}.TableName()+" WHERE created_at < datetime('now', ?)", "-"+string(rune(days))+" days") + deadline := time.Now().AddDate(0, 0, -days) + result := database.DB.Unscoped().Where("category = ? AND created_at < ?", constant.LogCategoryLoginLog, deadline).Delete(&models.AppLog{}) return result.RowsAffected, result.Error } diff --git a/internal/services/migration_v3.go b/internal/services/migration_v3.go index b9afb98..b316631 100644 --- a/internal/services/migration_v3.go +++ b/internal/services/migration_v3.go @@ -32,7 +32,6 @@ func getMigrationTables() []MigrationTable { {&models.Script{}, "scripts", map[string]string{"UserID": "users"}, nil}, {&models.Setting{}, "settings", nil, nil}, {&models.SendStats{}, "send_stats", map[string]string{"TaskID": "tasks"}, nil}, - {&models.LoginLog{}, "login_logs", nil, nil}, {&models.Language{}, "languages", nil, nil}, {&models.Dependency{}, "deps", nil, nil}, } diff --git a/web/src/components/SystemNotice.vue b/web/src/components/SystemNotice.vue index e8472a1..5377c43 100644 --- a/web/src/components/SystemNotice.vue +++ b/web/src/components/SystemNotice.vue @@ -88,25 +88,30 @@ function getLevelColor(level: string) {
- 系统通知 ({{ unreadCount }}) -
- +
-
+
- {{ notice.level || 'info' }} + {{ notice.level + || 'info' }} {{ notice.title }}

{{ notice.content }}

diff --git a/web/src/layouts/MainLayout.vue b/web/src/layouts/MainLayout.vue index 4839326..dea567b 100644 --- a/web/src/layouts/MainLayout.vue +++ b/web/src/layouts/MainLayout.vue @@ -62,7 +62,7 @@ const navItems = [ { to: '/languages', icon: Globe, label: '语言依赖', exact: true }, { to: '/terminal', icon: Terminal, label: '终端命令', exact: true }, { to: '/notify', icon: Bell, label: '消息推送', exact: true }, - { to: '/loginlogs', icon: KeyRound, label: '登录日志', exact: true }, + { to: '/logs', icon: KeyRound, label: '消息日志', exact: true }, { to: '/settings', icon: Settings, label: '系统设置', exact: true }, ] diff --git a/web/src/router/index.ts b/web/src/router/index.ts index d41ae04..2a4a195 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -45,7 +45,7 @@ const router = createRouter({ { path: 'languages', name: 'languages', component: () => import('@/views/languages/Languages.vue') }, { path: 'agents', name: 'agents', component: () => import('@/views/agents/Agents.vue') }, { path: 'history', name: 'history', component: () => import('@/views/history/History.vue') }, - { path: 'loginlogs', name: 'loginlogs', component: () => import('@/views/loginlogs/LoginLogs.vue') }, + { path: 'logs', name: 'logs', component: () => import('@/views/loginlogs/LoginLogs.vue') }, { path: 'terminal', name: 'terminal', component: () => import('@/views/terminal/Terminal.vue') }, { path: 'notify', name: 'notify', component: () => import('@/views/notify/Notify.vue') }, { path: 'settings', name: 'settings', component: () => import('@/views/settings/Settings.vue') } diff --git a/web/src/views/loginlogs/LoginLogs.vue b/web/src/views/loginlogs/LoginLogs.vue index e3da2a2..700017a 100644 --- a/web/src/views/loginlogs/LoginLogs.vue +++ b/web/src/views/loginlogs/LoginLogs.vue @@ -1,194 +1,42 @@ diff --git a/web/src/views/loginlogs/tabs/LoginLogTab.vue b/web/src/views/loginlogs/tabs/LoginLogTab.vue new file mode 100644 index 0000000..3f7b8cc --- /dev/null +++ b/web/src/views/loginlogs/tabs/LoginLogTab.vue @@ -0,0 +1,194 @@ + + + diff --git a/web/src/views/loginlogs/tabs/SystemEventTab.vue b/web/src/views/loginlogs/tabs/SystemEventTab.vue new file mode 100644 index 0000000..fdccb64 --- /dev/null +++ b/web/src/views/loginlogs/tabs/SystemEventTab.vue @@ -0,0 +1,284 @@ + + + diff --git a/web/src/views/notify/Notify.vue b/web/src/views/notify/Notify.vue index fab21c1..9255330 100644 --- a/web/src/views/notify/Notify.vue +++ b/web/src/views/notify/Notify.vue @@ -17,7 +17,6 @@ import ChannelList from './components/ChannelList.vue' import EventBinding from './components/EventBinding.vue' import ApiUsage from './components/ApiUsage.vue' import ChannelDialog from './components/ChannelDialog.vue' -import PushLog from './components/PushLog.vue' const activeTab = ref('channels') @@ -323,11 +322,10 @@ onMounted(() => {

消息推送

配置通知渠道,绑定系统事件实现自动推送

- - 渠道管理 - 事件绑定 - 推送日志 - 脚本调用 + + 渠道管理 + 事件绑定 + 脚本调用
@@ -349,10 +347,7 @@ onMounted(() => { @generate-token="generateApiToken" @copy-token="copyApiToken" @copy-example="copyApiExample" /> - - - - + diff --git a/web/src/views/notify/components/PushLog.vue b/web/src/views/notify/components/PushLog.vue index 2b5a79a..e36d59f 100644 --- a/web/src/views/notify/components/PushLog.vue +++ b/web/src/views/notify/components/PushLog.vue @@ -167,7 +167,8 @@ function onDialogClose(open: boolean) {
-
@@ -188,7 +189,7 @@ function onDialogClose(open: boolean) { 状态 发送时间
- +
@@ -196,22 +197,23 @@ function onDialogClose(open: boolean) {
+ :class="[selectedLogId === log.id && 'bg-accent/50']" @click="showDetail(log)"> #{{ getLogIndex(index) }} - {{ log.title }} + {{ log.title + }} - + {{ formatDate(log.created_at) }}
- + @@ -233,7 +235,7 @@ function onDialogClose(open: boolean) { - +
@@ -243,27 +245,31 @@ function onDialogClose(open: boolean) {
发生时间 - {{ selectedLog ? formatDate(selectedLog.created_at) : '-' }} + {{ selectedLog ? formatDate(selectedLog.created_at) + : '-' }}
-
+
推送内容
-
{{ detailDialogProps.content }}
无推送内容