feat: add scheduler log display
This commit is contained in:
@@ -60,6 +60,8 @@ const (
|
|||||||
KeyPushLogMaxCount = "push_log_max_count"
|
KeyPushLogMaxCount = "push_log_max_count"
|
||||||
KeyLoginLogDays = "login_log_days"
|
KeyLoginLogDays = "login_log_days"
|
||||||
KeyLoginLogMaxCount = "login_log_max_count"
|
KeyLoginLogMaxCount = "login_log_max_count"
|
||||||
|
KeySchedulerLogDays = "scheduler_log_days"
|
||||||
|
KeySchedulerLogMaxCount = "scheduler_log_max_count"
|
||||||
|
|
||||||
// Scheduler Settings Key 常量
|
// Scheduler Settings Key 常量
|
||||||
KeyWorkerCount = "worker_count"
|
KeyWorkerCount = "worker_count"
|
||||||
@@ -104,6 +106,7 @@ const (
|
|||||||
|
|
||||||
// 其他事件类型
|
// 其他事件类型
|
||||||
EventSystemNotice = "system_notice"
|
EventSystemNotice = "system_notice"
|
||||||
|
EventSchedulerLog = "scheduler_log"
|
||||||
EventNotifySent = "notify_sent"
|
EventNotifySent = "notify_sent"
|
||||||
EventAppLogAdded = "app_log_added"
|
EventAppLogAdded = "app_log_added"
|
||||||
|
|
||||||
@@ -153,6 +156,7 @@ const (
|
|||||||
LogCategorySystemNotice = "system_notice"
|
LogCategorySystemNotice = "system_notice"
|
||||||
LogCategoryPushLog = "push_log"
|
LogCategoryPushLog = "push_log"
|
||||||
LogCategoryLoginLog = "login_log"
|
LogCategoryLoginLog = "login_log"
|
||||||
|
LogCategorySchedulerLog = "scheduler_log"
|
||||||
|
|
||||||
// AppLog 级别
|
// AppLog 级别
|
||||||
LogLevelInfo = "info"
|
LogLevelInfo = "info"
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ func (sc *SettingsController) GetSiteSettings(c *gin.Context) {
|
|||||||
settings["push_log_max_count"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyPushLogMaxCount)
|
settings["push_log_max_count"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyPushLogMaxCount)
|
||||||
settings["login_log_days"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogDays)
|
settings["login_log_days"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogDays)
|
||||||
settings["login_log_max_count"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogMaxCount)
|
settings["login_log_max_count"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogMaxCount)
|
||||||
|
settings["scheduler_log_days"] = sc.settingsService.Get(constant.SectionSystem, constant.KeySchedulerLogDays)
|
||||||
|
settings["scheduler_log_max_count"] = sc.settingsService.Get(constant.SectionSystem, constant.KeySchedulerLogMaxCount)
|
||||||
|
|
||||||
utils.Success(c, settings)
|
utils.Success(c, settings)
|
||||||
}
|
}
|
||||||
@@ -188,6 +190,8 @@ func (sc *SettingsController) UpdateSiteSettings(c *gin.Context) {
|
|||||||
PushLogMaxCount string `json:"push_log_max_count"`
|
PushLogMaxCount string `json:"push_log_max_count"`
|
||||||
LoginLogDays string `json:"login_log_days"`
|
LoginLogDays string `json:"login_log_days"`
|
||||||
LoginLogMaxCount string `json:"login_log_max_count"`
|
LoginLogMaxCount string `json:"login_log_max_count"`
|
||||||
|
SchedulerLogDays string `json:"scheduler_log_days"`
|
||||||
|
SchedulerLogMaxCount string `json:"scheduler_log_max_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
@@ -228,6 +232,8 @@ func (sc *SettingsController) UpdateSiteSettings(c *gin.Context) {
|
|||||||
sc.settingsService.Set(constant.SectionSystem, constant.KeyPushLogMaxCount, req.PushLogMaxCount)
|
sc.settingsService.Set(constant.SectionSystem, constant.KeyPushLogMaxCount, req.PushLogMaxCount)
|
||||||
sc.settingsService.Set(constant.SectionSystem, constant.KeyLoginLogDays, req.LoginLogDays)
|
sc.settingsService.Set(constant.SectionSystem, constant.KeyLoginLogDays, req.LoginLogDays)
|
||||||
sc.settingsService.Set(constant.SectionSystem, constant.KeyLoginLogMaxCount, req.LoginLogMaxCount)
|
sc.settingsService.Set(constant.SectionSystem, constant.KeyLoginLogMaxCount, req.LoginLogMaxCount)
|
||||||
|
sc.settingsService.Set(constant.SectionSystem, constant.KeySchedulerLogDays, req.SchedulerLogDays)
|
||||||
|
sc.settingsService.Set(constant.SectionSystem, constant.KeySchedulerLogMaxCount, req.SchedulerLogMaxCount)
|
||||||
|
|
||||||
utils.SuccessMsg(c, "保存成功")
|
utils.SuccessMsg(c, "保存成功")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ func startAppLogCleanup(appLogSvc *services.AppLogService) {
|
|||||||
// 初始化时执行一次清理
|
// 初始化时执行一次清理
|
||||||
appLogSvc.CleanUp()
|
appLogSvc.CleanUp()
|
||||||
|
|
||||||
// 每天凌晨或者定期清理
|
// 定期清理(每隔1小时执行一次巡检)
|
||||||
ticker := time.NewTicker(24 * time.Hour)
|
ticker := time.NewTicker(1 * time.Hour)
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
appLogSvc.CleanUp()
|
appLogSvc.CleanUp()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/engigu/baihu-panel/internal/constant"
|
"github.com/engigu/baihu-panel/internal/constant"
|
||||||
"github.com/engigu/baihu-panel/internal/database"
|
"github.com/engigu/baihu-panel/internal/database"
|
||||||
@@ -133,6 +134,10 @@ func (s *AppLogService) GetRetentionConfigs() map[string]LogRetentionConfig {
|
|||||||
Days: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogDays), 30),
|
Days: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogDays), 30),
|
||||||
MaxCount: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogMaxCount), 1000),
|
MaxCount: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogMaxCount), 1000),
|
||||||
},
|
},
|
||||||
|
constant.LogCategorySchedulerLog: {
|
||||||
|
Days: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeySchedulerLogDays), 30),
|
||||||
|
MaxCount: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeySchedulerLogMaxCount), 10000),
|
||||||
|
},
|
||||||
constant.LogCategoryDefault: {
|
constant.LogCategoryDefault: {
|
||||||
Days: 30,
|
Days: 30,
|
||||||
MaxCount: 10000,
|
MaxCount: 10000,
|
||||||
@@ -141,9 +146,25 @@ func (s *AppLogService) GetRetentionConfigs() map[string]LogRetentionConfig {
|
|||||||
return configs
|
return configs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AppLogService) AddSchedulerLog(title, content, level string) error {
|
||||||
|
if level == "" {
|
||||||
|
level = constant.LogLevelInfo
|
||||||
|
}
|
||||||
|
return s.Add(&models.AppLog{
|
||||||
|
Category: constant.LogCategorySchedulerLog,
|
||||||
|
Title: title,
|
||||||
|
Content: models.BigText(content),
|
||||||
|
Level: level,
|
||||||
|
Status: constant.LogStatusRead,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *AppLogService) CleanUp() {
|
func (s *AppLogService) CleanUp() {
|
||||||
configs := s.GetRetentionConfigs()
|
configs := s.GetRetentionConfigs()
|
||||||
categories := []string{constant.LogCategorySystemNotice, constant.LogCategoryPushLog, constant.LogCategoryLoginLog}
|
categories := []string{constant.LogCategorySystemNotice, constant.LogCategoryPushLog, constant.LogCategoryLoginLog, constant.LogCategorySchedulerLog}
|
||||||
|
|
||||||
|
var totalDeleted int64
|
||||||
|
var summaryBuilder strings.Builder
|
||||||
|
|
||||||
for _, cat := range categories {
|
for _, cat := range categories {
|
||||||
cfg, ok := configs[cat]
|
cfg, ok := configs[cat]
|
||||||
@@ -151,11 +172,16 @@ func (s *AppLogService) CleanUp() {
|
|||||||
cfg = configs[constant.LogCategoryDefault]
|
cfg = configs[constant.LogCategoryDefault]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var daysDeleted int64
|
||||||
if cfg.Days > 0 {
|
if cfg.Days > 0 {
|
||||||
deadline := time.Now().AddDate(0, 0, -cfg.Days)
|
deadline := time.Now().AddDate(0, 0, -cfg.Days)
|
||||||
database.DB.Where("category = ? AND created_at < ?", cat, deadline).Delete(&models.AppLog{})
|
res := database.DB.Where("category = ? AND created_at < ?", cat, deadline).Delete(&models.AppLog{})
|
||||||
|
if res.Error == nil {
|
||||||
|
daysDeleted = res.RowsAffected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var countDeleted int64
|
||||||
if cfg.MaxCount > 0 {
|
if cfg.MaxCount > 0 {
|
||||||
var total int64
|
var total int64
|
||||||
database.DB.Model(&models.AppLog{}).Where("category = ?", cat).Count(&total)
|
database.DB.Model(&models.AppLog{}).Where("category = ?", cat).Count(&total)
|
||||||
@@ -164,12 +190,34 @@ func (s *AppLogService) CleanUp() {
|
|||||||
var ids []string
|
var ids []string
|
||||||
database.DB.Model(&models.AppLog{}).Where("category = ?", cat).Order("created_at asc").Limit(int(deleteCount)).Pluck("id", &ids)
|
database.DB.Model(&models.AppLog{}).Where("category = ?", cat).Order("created_at asc").Limit(int(deleteCount)).Pluck("id", &ids)
|
||||||
if len(ids) > 0 {
|
if len(ids) > 0 {
|
||||||
database.DB.Where("id IN ?", ids).Delete(&models.AppLog{})
|
res := database.DB.Where("id IN ?", ids).Delete(&models.AppLog{})
|
||||||
|
if res.Error == nil {
|
||||||
|
countDeleted = res.RowsAffected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catTotal := daysDeleted + countDeleted
|
||||||
|
if catTotal > 0 {
|
||||||
|
totalDeleted += catTotal
|
||||||
|
summaryBuilder.WriteString(fmt.Sprintf("分类 [%s]: 过期清理 %d 条,溢出限制清理 %d 条;\n", cat, daysDeleted, countDeleted))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalDeleted > 0 {
|
||||||
|
logger.Infof("[AppLog] 周期巡检完成清理,共删除 %d 条陈旧日志", totalDeleted)
|
||||||
|
eventbus.DefaultBus.Publish(eventbus.Event{
|
||||||
|
Type: constant.EventSystemNotice,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"title": "系统日志定时容量收敛",
|
||||||
|
"content": fmt.Sprintf("后台巡检已自动执行日志容量清理,共计清除陈旧或溢出记录 %d 条。\n详情明细:\n%s", totalDeleted, summaryBuilder.String()),
|
||||||
|
"level": constant.LogLevelInfo,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.Debugf("[AppLog] 完成应用日志清理策略,未检测到需要剔除的陈旧记录")
|
||||||
}
|
}
|
||||||
logger.Debugf("[AppLog] 完成应用日志清理策略")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AppLogService) SubscribeEvents(bus *eventbus.EventBus) {
|
func (s *AppLogService) SubscribeEvents(bus *eventbus.EventBus) {
|
||||||
@@ -281,4 +329,16 @@ func (s *AppLogService) SubscribeEvents(bus *eventbus.EventBus) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 4. [订阅] 调度日志写入
|
||||||
|
bus.Subscribe(constant.EventSchedulerLog, func(e eventbus.Event) {
|
||||||
|
payload, ok := e.Payload.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
title, _ := payload["title"].(string)
|
||||||
|
content, _ := payload["content"].(string)
|
||||||
|
level, _ := payload["level"].(string)
|
||||||
|
s.AddSchedulerLog(title, content, level)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ func (s *SettingsService) InitSettings() error {
|
|||||||
constant.KeyPushLogMaxCount: "5000",
|
constant.KeyPushLogMaxCount: "5000",
|
||||||
constant.KeyLoginLogDays: "30",
|
constant.KeyLoginLogDays: "30",
|
||||||
constant.KeyLoginLogMaxCount: "1000",
|
constant.KeyLoginLogMaxCount: "1000",
|
||||||
|
constant.KeySchedulerLogDays: "30",
|
||||||
|
constant.KeySchedulerLogMaxCount: "10000",
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range defaultRetention {
|
for k, v := range defaultRetention {
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ func (m *SystemWSManager) SubscribeEvents(bus *eventbus.EventBus) {
|
|||||||
bus.Subscribe(constant.EventSystemNotice, func(e eventbus.Event) {
|
bus.Subscribe(constant.EventSystemNotice, func(e eventbus.Event) {
|
||||||
m.Broadcast("notice", e.Payload)
|
m.Broadcast("notice", e.Payload)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 应用日志新增事件(驱动运行日志下属4大标签页实时流式刷新列表)
|
||||||
|
bus.Subscribe(constant.EventAppLogAdded, func(e eventbus.Event) {
|
||||||
|
m.Broadcast(e.Type, e.Payload)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientConnection) Close() {
|
func (c *ClientConnection) Close() {
|
||||||
|
|||||||
@@ -112,6 +112,15 @@ func (es *ExecutorService) initScheduler() {
|
|||||||
es.scheduler.Start()
|
es.scheduler.Start()
|
||||||
|
|
||||||
logger.Infof("[Executor] 调度器已启动: workers=%d, queue=%d, rate=%dms", workerCount, queueSize, rateInterval)
|
logger.Infof("[Executor] 调度器已启动: workers=%d, queue=%d, rate=%dms", workerCount, queueSize, rateInterval)
|
||||||
|
|
||||||
|
eventbus.DefaultBus.Publish(eventbus.Event{
|
||||||
|
Type: constant.EventSchedulerLog,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"title": "调度器启动",
|
||||||
|
"content": fmt.Sprintf("主服务任务调度引擎已成功拉起运行。\n配置 Worker 数量: %d\n等待队列容量: %d\n速率限制间隔: %dms", workerCount, queueSize, rateInterval),
|
||||||
|
"level": constant.LogLevelInfo,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerSchedulerHandler 实现 executor.SchedulerEventHandler
|
// ServerSchedulerHandler 实现 executor.SchedulerEventHandler
|
||||||
@@ -128,6 +137,15 @@ func (h *ServerSchedulerHandler) OnTaskScheduled(req *executor.ExecutionRequest)
|
|||||||
"status": constant.TaskStatusQueued,
|
"status": constant.TaskStatusQueued,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
eventbus.DefaultBus.Publish(eventbus.Event{
|
||||||
|
Type: constant.EventSchedulerLog,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"title": "触发任务",
|
||||||
|
"content": fmt.Sprintf("任务 [%s] (#%s) 调度类型: %s\n已成功推入后台调度排队队列等待执行。", req.Name, req.TaskID, req.Type),
|
||||||
|
"level": constant.LogLevelInfo,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,6 +592,15 @@ func (es *ExecutorService) loadCronTasks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Infof("[Executor] 启动调度已加载 %d 个定时任务", count)
|
logger.Infof("[Executor] 启动调度已加载 %d 个定时任务", count)
|
||||||
|
|
||||||
|
eventbus.DefaultBus.Publish(eventbus.Event{
|
||||||
|
Type: constant.EventSchedulerLog,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"title": "加载定时任务",
|
||||||
|
"content": fmt.Sprintf("后台调度引擎成功解析并载入本地活跃定时计划任务: %d 个。", count),
|
||||||
|
"level": constant.LogLevelInfo,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload 重新加载配置并重建调度器
|
// Reload 重新加载配置并重建调度器
|
||||||
@@ -588,6 +615,15 @@ func (es *ExecutorService) Reload() {
|
|||||||
if es.cronManager != nil {
|
if es.cronManager != nil {
|
||||||
es.cronManager.SetScheduler(es.scheduler)
|
es.cronManager.SetScheduler(es.scheduler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventbus.DefaultBus.Publish(eventbus.Event{
|
||||||
|
Type: constant.EventSchedulerLog,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"title": "调度器重载",
|
||||||
|
"content": "主服务任务调度引擎配置及执行队列实例已全量重载完毕。",
|
||||||
|
"level": constant.LogLevelInfo,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateExecutionRequest 统一处理任务到执行请求的转换逻辑(包含指令拼装、脱敏等)
|
// CreateExecutionRequest 统一处理任务到执行请求的转换逻辑(包含指令拼装、脱敏等)
|
||||||
|
|||||||
@@ -510,6 +510,8 @@ export interface SiteSettings {
|
|||||||
push_log_max_count?: string
|
push_log_max_count?: string
|
||||||
login_log_days?: string
|
login_log_days?: string
|
||||||
login_log_max_count?: string
|
login_log_max_count?: string
|
||||||
|
scheduler_log_days?: string
|
||||||
|
scheduler_log_max_count?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SchedulerSettings {
|
export interface SchedulerSettings {
|
||||||
@@ -663,7 +665,8 @@ export interface AppLogListResponse {
|
|||||||
export const LOG_CATEGORY = {
|
export const LOG_CATEGORY = {
|
||||||
SYSTEM_NOTICE: 'system_notice',
|
SYSTEM_NOTICE: 'system_notice',
|
||||||
PUSH_LOG: 'push_log',
|
PUSH_LOG: 'push_log',
|
||||||
LOGIN_LOG: 'login_log'
|
LOGIN_LOG: 'login_log',
|
||||||
|
SCHEDULER_LOG: 'scheduler_log'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const LOG_LEVEL = {
|
export const LOG_LEVEL = {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const navItems = [
|
|||||||
{ to: '/languages', icon: Globe, label: '语言依赖', exact: true },
|
{ to: '/languages', icon: Globe, label: '语言依赖', exact: true },
|
||||||
{ to: '/terminal', icon: Terminal, label: '终端命令', exact: true },
|
{ to: '/terminal', icon: Terminal, label: '终端命令', exact: true },
|
||||||
{ to: '/notify', icon: Bell, label: '消息推送', exact: true },
|
{ to: '/notify', icon: Bell, label: '消息推送', exact: true },
|
||||||
{ to: '/logs', icon: KeyRound, label: '消息日志', exact: true },
|
{ to: '/logs', icon: KeyRound, label: '运行日志', exact: true },
|
||||||
{ to: '/settings', icon: Settings, label: '系统设置', exact: true },
|
{ to: '/settings', icon: Settings, label: '系统设置', exact: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ import { Search, RefreshCw, Trash2 } from 'lucide-vue-next'
|
|||||||
import LoginLogTab from './tabs/LoginLogTab.vue'
|
import LoginLogTab from './tabs/LoginLogTab.vue'
|
||||||
import SystemEventTab from './tabs/SystemEventTab.vue'
|
import SystemEventTab from './tabs/SystemEventTab.vue'
|
||||||
import PushLogTab from './tabs/PushLogTab.vue'
|
import PushLogTab from './tabs/PushLogTab.vue'
|
||||||
|
import SchedulerLogTab from './tabs/SchedulerLogTab.vue'
|
||||||
import { LOG_LEVEL, LOG_STATUS } from '@/api'
|
import { LOG_LEVEL, LOG_STATUS } from '@/api'
|
||||||
|
|
||||||
const activeTab = ref('system')
|
const activeTab = ref('system')
|
||||||
const systemTabRef = ref()
|
const systemTabRef = ref()
|
||||||
const pushLogRef = ref()
|
const pushLogRef = ref()
|
||||||
const loginTabRef = ref()
|
const loginTabRef = ref()
|
||||||
|
const schedulerTabRef = ref()
|
||||||
|
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
system: { keyword: '', level: 'all' },
|
system: { keyword: '', level: 'all' },
|
||||||
push: { keyword: '', status: 'all' },
|
push: { keyword: '', status: 'all' },
|
||||||
login: { username: '' }
|
login: { username: '' },
|
||||||
|
scheduler: { keyword: '', level: 'all' }
|
||||||
})
|
})
|
||||||
|
|
||||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
@@ -39,6 +42,7 @@ async function handleRefresh() {
|
|||||||
if (activeTab.value === 'system') await systemTabRef.value?.fetchLogs()
|
if (activeTab.value === 'system') await systemTabRef.value?.fetchLogs()
|
||||||
else if (activeTab.value === 'push') await pushLogRef.value?.fetchLogs()
|
else if (activeTab.value === 'push') await pushLogRef.value?.fetchLogs()
|
||||||
else if (activeTab.value === 'login') await loginTabRef.value?.loadLogs()
|
else if (activeTab.value === 'login') await loginTabRef.value?.loadLogs()
|
||||||
|
else if (activeTab.value === 'scheduler') await schedulerTabRef.value?.fetchLogs()
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isRefreshing.value = false
|
isRefreshing.value = false
|
||||||
@@ -49,6 +53,7 @@ async function handleRefresh() {
|
|||||||
function handleClear() {
|
function handleClear() {
|
||||||
if (activeTab.value === 'system' && systemTabRef.value) systemTabRef.value.showClearConfirm = true
|
if (activeTab.value === 'system' && systemTabRef.value) systemTabRef.value.showClearConfirm = true
|
||||||
else if (activeTab.value === 'push' && pushLogRef.value) pushLogRef.value.showClearConfirm = true
|
else if (activeTab.value === 'push' && pushLogRef.value) pushLogRef.value.showClearConfirm = true
|
||||||
|
else if (activeTab.value === 'scheduler' && schedulerTabRef.value) schedulerTabRef.value.showClearConfirm = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换标签时重置搜索
|
// 切换标签时重置搜索
|
||||||
@@ -62,17 +67,18 @@ watch(activeTab, () => {
|
|||||||
<div class="space-y-6 h-full flex flex-col">
|
<div class="space-y-6 h-full flex flex-col">
|
||||||
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4 shrink-0 px-1">
|
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4 shrink-0 px-1">
|
||||||
<div class="flex flex-col shrink-0">
|
<div class="flex flex-col shrink-0">
|
||||||
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">消息日志</h2>
|
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">运行日志</h2>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
{{ activeTab === 'system' ? '查看系统重要运行事件' :
|
{{ activeTab === 'system' ? '查看系统重要运行事件' :
|
||||||
activeTab === 'push' ? '查看消息推送历史记录' : '查看系统用户登录记录' }}
|
activeTab === 'push' ? '查看消息推送历史记录' :
|
||||||
|
activeTab === 'scheduler' ? '查看后台调度器执行与配置装载日志' : '查看系统用户登录记录' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="[activeTab === 'login' ? 'flex flex-row lg:flex-row' : 'flex flex-col lg:flex-row', 'lg:items-center gap-2 lg:gap-3 w-full lg:w-auto lg:ml-auto lg:justify-end']">
|
<div :class="[activeTab === 'login' ? 'flex flex-row lg:flex-row' : 'flex flex-col lg:flex-row', 'lg:items-center gap-2 lg:gap-3 w-full lg:w-auto lg:ml-auto lg:justify-end']">
|
||||||
<!-- 搜索与筛选区域 -->
|
<!-- 搜索与筛选区域 -->
|
||||||
<div :class="[activeTab === 'login' ? 'flex-1 min-w-0' : 'w-full lg:w-auto', 'flex items-center gap-2']">
|
<div :class="[activeTab === 'login' ? 'flex-1 min-w-0' : 'w-full lg:w-auto', 'flex items-center gap-2']">
|
||||||
<!-- 系统事件 / 推送日志 搜索框 -->
|
<!-- 系统事件 / 推送日志 / 调度日志 搜索框 -->
|
||||||
<div v-if="activeTab !== 'login'" class="relative flex-1 lg:w-60 group">
|
<div v-if="activeTab !== 'login'" class="relative flex-1 lg:w-60 group">
|
||||||
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground group-focus-within:text-primary transition-colors" />
|
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground group-focus-within:text-primary transition-colors" />
|
||||||
<Input
|
<Input
|
||||||
@@ -89,6 +95,13 @@ watch(activeTab, () => {
|
|||||||
class="h-9 pl-9 w-full bg-muted/20 border-muted-foreground/10 focus:bg-background text-sm"
|
class="h-9 pl-9 w-full bg-muted/20 border-muted-foreground/10 focus:bg-background text-sm"
|
||||||
@input="handleSearch"
|
@input="handleSearch"
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
v-else-if="activeTab === 'scheduler'"
|
||||||
|
v-model="filters.scheduler.keyword"
|
||||||
|
placeholder="搜索调度日志..."
|
||||||
|
class="h-9 pl-9 w-full bg-muted/20 border-muted-foreground/10 focus:bg-background text-sm"
|
||||||
|
@input="handleSearch"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- 登录日志 搜索框 -->
|
<!-- 登录日志 搜索框 -->
|
||||||
<div v-else class="relative flex-1 lg:w-48 group">
|
<div v-else class="relative flex-1 lg:w-48 group">
|
||||||
@@ -146,6 +159,7 @@ watch(activeTab, () => {
|
|||||||
<Tabs v-model="activeTab" class="w-auto">
|
<Tabs v-model="activeTab" class="w-auto">
|
||||||
<TabsList class="h-9 p-1 bg-muted/30 border shrink-0 hidden lg:flex">
|
<TabsList class="h-9 p-1 bg-muted/30 border shrink-0 hidden lg:flex">
|
||||||
<TabsTrigger value="system" class="px-4 h-7 text-sm">系统事件</TabsTrigger>
|
<TabsTrigger value="system" class="px-4 h-7 text-sm">系统事件</TabsTrigger>
|
||||||
|
<TabsTrigger value="scheduler" class="px-4 h-7 text-sm">调度日志</TabsTrigger>
|
||||||
<TabsTrigger value="push" class="px-4 h-7 text-sm">推送日志</TabsTrigger>
|
<TabsTrigger value="push" class="px-4 h-7 text-sm">推送日志</TabsTrigger>
|
||||||
<TabsTrigger value="login" class="px-4 h-7 text-sm">登录日志</TabsTrigger>
|
<TabsTrigger value="login" class="px-4 h-7 text-sm">登录日志</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
@@ -159,6 +173,7 @@ watch(activeTab, () => {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="system">系统事件</SelectItem>
|
<SelectItem value="system">系统事件</SelectItem>
|
||||||
|
<SelectItem value="scheduler">调度日志</SelectItem>
|
||||||
<SelectItem value="push">推送日志</SelectItem>
|
<SelectItem value="push">推送日志</SelectItem>
|
||||||
<SelectItem value="login">登录日志</SelectItem>
|
<SelectItem value="login">登录日志</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -173,6 +188,10 @@ watch(activeTab, () => {
|
|||||||
<SystemEventTab ref="systemTabRef" :filters="filters.system" />
|
<SystemEventTab ref="systemTabRef" :filters="filters.system" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-show="activeTab === 'scheduler'" class="h-full">
|
||||||
|
<SchedulerLogTab ref="schedulerTabRef" :filters="filters.scheduler" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-show="activeTab === 'push'" class="h-full">
|
<div v-show="activeTab === 'push'" class="h-full">
|
||||||
<PushLogTab ref="pushLogRef" :filters="filters.push" />
|
<PushLogTab ref="pushLogRef" :filters="filters.push" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,296 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { api, type AppLog, LOG_CATEGORY, LOG_LEVEL } from '@/api'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
Info, AlertTriangle, AlertCircle
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import Pagination from '@/components/Pagination.vue'
|
||||||
|
import BaihuDialog from '@/components/ui/BaihuDialog.vue'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
|
import { useEventBus } from '@/composables/useEventBus'
|
||||||
|
import { LOG_EVENTS } from '@/constants'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
filters: {
|
||||||
|
level: string
|
||||||
|
keyword: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { pageSize } = useSiteSettings()
|
||||||
|
|
||||||
|
const logs = ref<AppLog[]>([])
|
||||||
|
const selectedLogId = ref<string | null>(null)
|
||||||
|
const total = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
const showClearConfirm = ref(false)
|
||||||
|
const currentPage = ref(1)
|
||||||
|
|
||||||
|
const detailDialogProps = ref({
|
||||||
|
open: false,
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
error: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
async function fetchLogs() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await api.appLogs.list({
|
||||||
|
category: LOG_CATEGORY.SCHEDULER_LOG,
|
||||||
|
level: props.filters.level === 'all' ? undefined : props.filters.level,
|
||||||
|
keyword: props.filters.keyword || undefined,
|
||||||
|
page: currentPage.value,
|
||||||
|
page_size: pageSize.value
|
||||||
|
})
|
||||||
|
logs.value = res.data || []
|
||||||
|
total.value = res.total || 0
|
||||||
|
} catch (e: any) {
|
||||||
|
toast.error(e.message || '获取调度日志失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageChange(index: number) {
|
||||||
|
currentPage.value = index
|
||||||
|
fetchLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDetail(log: AppLog) {
|
||||||
|
selectedLogId.value = log.id
|
||||||
|
detailDialogProps.value = {
|
||||||
|
open: true,
|
||||||
|
title: log.title,
|
||||||
|
content: log.content,
|
||||||
|
error: log.error_msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleClear() {
|
||||||
|
try {
|
||||||
|
await api.appLogs.clear(LOG_CATEGORY.SCHEDULER_LOG)
|
||||||
|
toast.success('清空成功')
|
||||||
|
currentPage.value = 1
|
||||||
|
fetchLogs()
|
||||||
|
} catch (e: any) {
|
||||||
|
toast.error('清空失败: ' + (e.message || ''))
|
||||||
|
}
|
||||||
|
showClearConfirm.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchLogs()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 实时更新:当有新调度日志产生且用户在第一页时刷新
|
||||||
|
useEventBus([LOG_EVENTS.ADDED], (payload) => {
|
||||||
|
if (payload && payload.category === LOG_CATEGORY.SCHEDULER_LOG) {
|
||||||
|
if (currentPage.value === 1) {
|
||||||
|
fetchLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedLog = computed(() => logs.value.find((l: AppLog) => l.id === selectedLogId.value))
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
fetchLogs,
|
||||||
|
showClearConfirm
|
||||||
|
})
|
||||||
|
|
||||||
|
function getLevelBadgeClass(level: string) {
|
||||||
|
switch (level) {
|
||||||
|
case LOG_LEVEL.INFO:
|
||||||
|
return 'bg-blue-500/15 text-blue-500 border-blue-500/30'
|
||||||
|
case LOG_LEVEL.WARNING:
|
||||||
|
return 'bg-amber-500/15 text-amber-500 border-amber-500/30'
|
||||||
|
case LOG_LEVEL.ERROR:
|
||||||
|
return 'bg-red-500/15 text-red-500 border-red-500/30'
|
||||||
|
default:
|
||||||
|
return 'bg-secondary text-secondary-foreground border-transparent'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLevelIcon(level: string) {
|
||||||
|
switch (level) {
|
||||||
|
case LOG_LEVEL.INFO:
|
||||||
|
return Info
|
||||||
|
case LOG_LEVEL.WARNING:
|
||||||
|
return AlertTriangle
|
||||||
|
case LOG_LEVEL.ERROR:
|
||||||
|
return AlertCircle
|
||||||
|
default:
|
||||||
|
return Info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateStr: string) {
|
||||||
|
if (!dateStr) return '-'
|
||||||
|
try {
|
||||||
|
return format(new Date(dateStr), 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
} catch {
|
||||||
|
return dateStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDialogClose(open: boolean) {
|
||||||
|
if (!open) {
|
||||||
|
selectedLogId.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<BaihuDialog v-model:open="showClearConfirm" title="清空调度日志确认">
|
||||||
|
<div class="text-sm text-muted-foreground leading-relaxed">
|
||||||
|
此操作将永久清空当前分类下的所有调度日志记录,操作后无法恢复。确认要继续吗?
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<Button variant="ghost" @click="showClearConfirm = false">取消</Button>
|
||||||
|
<Button variant="destructive" class="shadow-lg shadow-destructive/20" @click="handleClear">确认清空</Button>
|
||||||
|
</template>
|
||||||
|
</BaihuDialog>
|
||||||
|
|
||||||
|
<div class="rounded-lg border bg-card overflow-hidden">
|
||||||
|
<!-- ========== 1. 大屏表头 (Large >= 1024px) ========== -->
|
||||||
|
<div class="hidden lg:flex items-center gap-4 px-4 py-2 border-b bg-muted/20 text-sm text-muted-foreground font-medium">
|
||||||
|
<span class="w-16 shrink-0 pl-1">序号</span>
|
||||||
|
<span class="w-56 shrink-0 px-2 pl-8">事件信息</span>
|
||||||
|
<span class="flex-1 min-w-0 px-2">详情内容</span>
|
||||||
|
<span class="w-40 shrink-0 text-right">发生时间</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ========== 2. 中屏表头 (Medium 640px - 1024px) ========== -->
|
||||||
|
<div class="hidden sm:flex lg:hidden items-center gap-4 px-4 py-2 border-b bg-muted/20 text-sm text-muted-foreground font-medium">
|
||||||
|
<span class="w-60 shrink-0">事件信息</span>
|
||||||
|
<span class="flex-1 min-w-0">详情内容</span>
|
||||||
|
<span class="w-40 shrink-0 text-right">发生时间</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表内容 -->
|
||||||
|
<div class="divide-y">
|
||||||
|
<div v-if="logs.length === 0 && !loading" class="text-sm text-muted-foreground text-center py-8">
|
||||||
|
暂无调度日志
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ========== 1. 小屏布局 (Small < 640px) - 统一风格 ========== -->
|
||||||
|
<div v-for="(log, index) in logs" :key="`small-${log.id}`"
|
||||||
|
class="sm:hidden p-3 hover:bg-muted/50 transition-colors cursor-pointer group"
|
||||||
|
:class="[selectedLogId === log.id && 'bg-accent/50']" @click="showDetail(log)">
|
||||||
|
<div class="flex items-start justify-between mb-3 border-b border-border/40 pb-2">
|
||||||
|
<div class="flex items-center gap-2 flex-1 min-w-0 mr-2">
|
||||||
|
<span class="text-xs text-muted-foreground shrink-0 tabular-nums">#{{ total - (currentPage - 1) * pageSize - index }}</span>
|
||||||
|
<span class="font-bold text-sm truncate" :title="log.title">{{ log.title }}</span>
|
||||||
|
</div>
|
||||||
|
<span :class="['h-2 w-2 mt-1.5 rounded-full shrink-0 shadow-[0_0_8px]',
|
||||||
|
log.level === LOG_LEVEL.INFO ? 'bg-blue-500 shadow-blue-500/40' :
|
||||||
|
log.level === LOG_LEVEL.WARNING ? 'bg-yellow-500 shadow-yellow-500/40' : 'bg-red-500 shadow-red-500/40']"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情信息列表 -->
|
||||||
|
<div class="space-y-1.5 text-xs text-muted-foreground mb-1 px-1">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-8 shrink-0 font-medium opacity-70">级别:</span>
|
||||||
|
<span :class="['px-1.5 py-0.5 rounded text-[10px] font-medium',
|
||||||
|
log.level === LOG_LEVEL.INFO ? 'bg-blue-500/10 text-blue-500' :
|
||||||
|
log.level === LOG_LEVEL.WARNING ? 'bg-yellow-500/10 text-yellow-500' : 'bg-red-500/10 text-red-500']">
|
||||||
|
{{ log.level === LOG_LEVEL.INFO ? '信息' : log.level === LOG_LEVEL.WARNING ? '警告' : '错误' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<span class="w-8 shrink-0 font-medium mt-0.5 opacity-70">内容:</span>
|
||||||
|
<div class="flex-1 min-w-0 text-foreground break-all leading-relaxed line-clamp-2">
|
||||||
|
{{ log.content || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-8 shrink-0 font-medium opacity-70">时间:</span>
|
||||||
|
<span class="text-[10px] text-muted-foreground">{{ formatDate(log.created_at) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ========== 2. 中屏布局 (Medium 640px - 1024px) ========== -->
|
||||||
|
<div v-for="log in logs" :key="`medium-${log.id}`"
|
||||||
|
class="hidden sm:flex lg:hidden items-center gap-4 px-4 py-2.5 hover:bg-muted/50 transition-colors cursor-pointer group"
|
||||||
|
:class="[selectedLogId === log.id && 'bg-accent/50']" @click="showDetail(log)">
|
||||||
|
<div class="w-60 shrink-0 flex items-center gap-3 min-w-0 font-medium text-sm">
|
||||||
|
<component :is="getLevelIcon(log.level)" :class="['h-3.5 w-3.5 shrink-0 opacity-80',
|
||||||
|
log.level === LOG_LEVEL.INFO ? 'text-blue-500' :
|
||||||
|
log.level === LOG_LEVEL.WARNING ? 'text-yellow-500' : 'text-red-500']" />
|
||||||
|
<span class="truncate" :title="log.title">{{ log.title }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="flex-1 min-w-0 text-sm text-muted-foreground line-clamp-1" :title="log.content">
|
||||||
|
{{ log.content || '-' }}
|
||||||
|
</span>
|
||||||
|
<span class="w-40 shrink-0 text-right text-xs text-muted-foreground tabular-nums opacity-60">
|
||||||
|
{{ formatDate(log.created_at) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-for="(log, index) in logs" :key="`large-${log.id}`"
|
||||||
|
class="hidden lg:flex items-center gap-4 px-4 py-2 hover:bg-muted/50 transition-colors cursor-pointer group"
|
||||||
|
:class="[selectedLogId === log.id && 'bg-accent/50']" @click="showDetail(log)">
|
||||||
|
<span class="w-16 shrink-0 text-muted-foreground text-[13px] tabular-nums pl-1">#{{ total - (currentPage - 1) * pageSize - index }}</span>
|
||||||
|
<div class="w-56 shrink-0 flex items-center gap-3 min-w-0 text-[13px]">
|
||||||
|
<component :is="getLevelIcon(log.level)" :class="['h-4 w-4 shrink-0 opacity-80',
|
||||||
|
log.level === LOG_LEVEL.INFO ? 'text-blue-500' :
|
||||||
|
log.level === LOG_LEVEL.WARNING ? 'text-yellow-500' : 'text-red-500']" />
|
||||||
|
<span class="truncate" :title="log.title">{{ log.title }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="flex-1 min-w-0 text-[13px] text-muted-foreground truncate"
|
||||||
|
:title="log.content">
|
||||||
|
{{ log.content || '-' }}
|
||||||
|
</span>
|
||||||
|
<span class="w-40 shrink-0 text-right text-[13px] text-muted-foreground tabular-nums opacity-60">
|
||||||
|
{{ formatDate(log.created_at) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Pagination :total="total" :page="currentPage" @update:page="handlePageChange" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BaihuDialog v-model:open="detailDialogProps.open" :title="detailDialogProps.title" @update:open="onDialogClose">
|
||||||
|
<template #description>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Badge v-if="selectedLog" variant="outline" :class="[
|
||||||
|
'px-2 py-0.5 text-[10px] font-bold rounded-md border shadow-sm',
|
||||||
|
getLevelBadgeClass(selectedLog.level)
|
||||||
|
]">
|
||||||
|
<div class="flex items-center gap-1 uppercase tracking-tighter">
|
||||||
|
<component :is="getLevelIcon(selectedLog.level)" class="h-3 w-3" />
|
||||||
|
<span>{{ selectedLog.level === LOG_LEVEL.INFO ? '信息' : selectedLog.level === LOG_LEVEL.WARNING ? '警告' : '错误' }}</span>
|
||||||
|
</div>
|
||||||
|
</Badge>
|
||||||
|
<span class="text-[10px] text-muted-foreground font-mono">{{ selectedLog ? formatDate(selectedLog.created_at) : '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="p-4 rounded-xl bg-muted/20 border border-border/10 space-y-2">
|
||||||
|
<p class="text-[10px] uppercase tracking-widest text-muted-foreground font-bold">事件内容</p>
|
||||||
|
<div v-if="detailDialogProps.content" class="text-sm leading-relaxed text-foreground/80 break-all whitespace-pre-wrap">
|
||||||
|
{{ detailDialogProps.content }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sm text-muted-foreground italic">无内容</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="detailDialogProps.error" class="p-4 rounded-xl bg-destructive/5 border border-destructive/10 space-y-2">
|
||||||
|
<p class="text-[10px] uppercase tracking-widest text-destructive font-bold">错误堆栈/信息</p>
|
||||||
|
<div class="text-sm leading-relaxed text-destructive/90 break-all whitespace-pre-wrap font-mono bg-destructive/5 p-3 rounded-lg border border-destructive/10">
|
||||||
|
{{ detailDialogProps.error }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaihuDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -36,7 +36,9 @@ const form = ref<SiteSettings>({
|
|||||||
push_log_days: '15',
|
push_log_days: '15',
|
||||||
push_log_max_count: '5000',
|
push_log_max_count: '5000',
|
||||||
login_log_days: '30',
|
login_log_days: '30',
|
||||||
login_log_max_count: '1000'
|
login_log_max_count: '1000',
|
||||||
|
scheduler_log_days: '30',
|
||||||
|
scheduler_log_max_count: '10000'
|
||||||
})
|
})
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const showOpenapiConfirmDialog = ref(false)
|
const showOpenapiConfirmDialog = ref(false)
|
||||||
@@ -72,7 +74,9 @@ async function saveSettings() {
|
|||||||
push_log_days: String(form.value.push_log_days || '15'),
|
push_log_days: String(form.value.push_log_days || '15'),
|
||||||
push_log_max_count: String(form.value.push_log_max_count || '5000'),
|
push_log_max_count: String(form.value.push_log_max_count || '5000'),
|
||||||
login_log_days: String(form.value.login_log_days || '30'),
|
login_log_days: String(form.value.login_log_days || '30'),
|
||||||
login_log_max_count: String(form.value.login_log_max_count || '1000')
|
login_log_max_count: String(form.value.login_log_max_count || '1000'),
|
||||||
|
scheduler_log_days: String(form.value.scheduler_log_days || '30'),
|
||||||
|
scheduler_log_max_count: String(form.value.scheduler_log_max_count || '10000')
|
||||||
})
|
})
|
||||||
await refreshSettings()
|
await refreshSettings()
|
||||||
await loadSettings()
|
await loadSettings()
|
||||||
@@ -207,6 +211,23 @@ onMounted(loadSettings)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-4 items-start sm:items-center gap-2 sm:gap-4">
|
||||||
|
<Label class="sm:text-right text-muted-foreground whitespace-nowrap sm:pt-0 pt-1">调度日志</Label>
|
||||||
|
<div class="sm:col-span-3 grid grid-cols-2 gap-0">
|
||||||
|
<div class="flex items-center gap-1.5 pr-4">
|
||||||
|
<Input v-model="form.scheduler_log_days" type="number" class="w-full h-8 text-xs sm:h-9 sm:text-sm"
|
||||||
|
min="0" />
|
||||||
|
<span class="text-xs sm:text-sm text-muted-foreground whitespace-nowrap">天清理</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1.5 border-l pl-4 border-border/50">
|
||||||
|
<span class="text-xs sm:text-sm text-muted-foreground whitespace-nowrap">保留</span>
|
||||||
|
<Input v-model="form.scheduler_log_max_count" type="number" class="w-full h-full h-8 text-xs sm:h-9 sm:text-sm"
|
||||||
|
min="0" />
|
||||||
|
<span class="text-xs sm:text-sm text-muted-foreground whitespace-nowrap">条</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 p-4 bg-muted/30 rounded-lg border border-dashed border-border flex flex-col gap-3">
|
<div class="mt-6 p-4 bg-muted/30 rounded-lg border border-dashed border-border flex flex-col gap-3">
|
||||||
@@ -228,7 +249,7 @@ onMounted(loadSettings)
|
|||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<p class="text-sm font-medium">执行周期说明</p>
|
<p class="text-sm font-medium">执行周期说明</p>
|
||||||
<p class="text-xs text-muted-foreground leading-relaxed">
|
<p class="text-xs text-muted-foreground leading-relaxed">
|
||||||
清理任务在白虎面板后端服务启动时立即执行一次。在运行期间,系统将自动开启后台巡检计数器,每隔 24 小时进行周期性自动清理。
|
清理任务在白虎面板后端服务启动时立即执行一次。在运行期间,系统将自动开启后台巡检计数器,每隔 1 小时进行周期性自动清理。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user