issue-ai/src/app/api/monitor/status/route.ts

100 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/app/api/monitor/status/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { getDb } from '@/lib/db'
import { initDatabase } from '@/lib/db-schema'
import { getCurrentUser } from '@/lib/auth'
import { hasPermission } from '@/lib/permissions'
import { getSetting } from '@/lib/monitor/settings-manager'
export async function GET(request: NextRequest) {
try {
initDatabase()
const user = await getCurrentUser()
if (!user || !hasPermission(user, 'monitor:read')) {
return NextResponse.json({ error: '权限不足' }, { status: 403 })
}
const db = getDb()
const enabled = getSetting('monitor.enabled') === 'true'
const lastTick = db.prepare("SELECT created_at, details FROM monitor_logs WHERE action = 'tick_complete' ORDER BY id DESC LIMIT 1").get() as { created_at: string; details: string } | undefined
// 使用 UTC+8 日期(数据库中 created_at 是 UTC+8
const now = new Date()
const todayStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 00:00:00`
const todayProcessed = (db.prepare("SELECT COUNT(*) as c FROM monitor_logs WHERE action = 'ticket_created' AND created_at >= ?").get(todayStart) as { c: number }).c
const totalProcessed = (db.prepare("SELECT COUNT(*) as c FROM monitor_logs WHERE action = 'ticket_created'").get() as { c: number }).c
// 最近一次 tick 的错误状态action='error' → 1tick_complete → 该 tick 的 errors 数;无记录 → 0
const lastLog = db.prepare(
"SELECT action, details FROM monitor_logs WHERE action IN ('error', 'tick_complete') ORDER BY id DESC LIMIT 1"
).get() as { action: string; details: string | null } | undefined
let errorCount = 0
if (lastLog) {
if (lastLog.action === 'error') {
errorCount = 1
} else {
try { errorCount = JSON.parse(lastLog.details || '{}').errors || 0 } catch { errorCount = 0 }
}
}
const ongoingFaultsCount = (db.prepare("SELECT COUNT(*) as c FROM fault_records WHERE status = 'ongoing'").get() as { c: number }).c
// 获取最近的活动tick_complete 或 error
const lastActivity = db.prepare("SELECT created_at, action FROM monitor_logs WHERE action IN ('tick_complete', 'error') ORDER BY id DESC LIMIT 1").get() as { created_at: string; action: string } | undefined
let workerHealth = 'unknown'
if (enabled && lastActivity) {
const elapsed = Date.now() - new Date(lastActivity.created_at.replace(' ', 'T') + '+08:00').getTime()
if (elapsed < 180_000) {
workerHealth = lastActivity.action === 'error' ? 'error' : 'healthy'
} else {
workerHealth = 'unresponsive'
}
} else if (!enabled) {
workerHealth = 'stopped'
}
const lastError = db.prepare("SELECT details, created_at FROM monitor_logs WHERE action = 'error' ORDER BY id DESC LIMIT 1").get() as { details: string; created_at: string } | undefined
// 今日处理详情:从 monitor_logs 获取今天的工单创建记录
const todayProcessedDetails = db.prepare(
"SELECT details, created_at FROM monitor_logs WHERE action = 'ticket_created' AND created_at >= ? ORDER BY id DESC LIMIT 20"
).all(todayStart) as { details: string; created_at: string }[]
// 进行中故障详情
const ongoingFaultsDetails = db.prepare(
"SELECT * FROM fault_records WHERE status = 'ongoing' ORDER BY fault_time DESC LIMIT 20"
).all() as Record<string, unknown>[]
// 最近错误列表
const recentErrors = db.prepare(
"SELECT details, created_at FROM monitor_logs WHERE action = 'error' ORDER BY id DESC LIMIT 10"
).all() as { details: string; created_at: string }[]
return NextResponse.json({
enabled,
status: enabled ? 'running' : 'stopped',
workerHealth,
lastCheck: lastTick?.created_at || null,
stats: {
totalProcessed,
todayProcessed,
errors: errorCount,
lastError: lastError?.details || null,
lastErrorTime: lastError?.created_at || null,
},
ongoingFaults: ongoingFaultsCount,
details: {
todayProcessed: todayProcessedDetails.map(d => {
try { return { ...JSON.parse(d.details), created_at: d.created_at } }
catch { return { details: d.details, created_at: d.created_at } }
}),
ongoingFaults: ongoingFaultsDetails,
recentErrors: recentErrors.map(d => {
try { return { ...JSON.parse(d.details), created_at: d.created_at } }
catch { return { details: d.details, created_at: d.created_at } }
}),
},
})
} catch (e) {
return NextResponse.json({ error: e instanceof Error ? e.message : 'Internal error' }, { status: 500 })
}
}