188 lines
5.1 KiB
TypeScript
188 lines
5.1 KiB
TypeScript
import { query } from '../../utils/db'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const queryParams = getQuery(event)
|
|
|
|
const type = queryParams.type as string || 'short-term'
|
|
const period = queryParams.period as string || '24h'
|
|
|
|
// 기간/간격 설정
|
|
const config = getPeriodConfig(period)
|
|
|
|
// 시간 슬롯 생성 (연속된 X축)
|
|
const timeSlots = generateTimeSlots(config)
|
|
|
|
// DB에서 로그 조회
|
|
let rows: any[] = []
|
|
try {
|
|
rows = await query<any>(`
|
|
SELECT
|
|
to_char(detected_at::timestamp, '${config.dbFormat}') as time_slot,
|
|
SUM(CASE WHEN level = 'warning' THEN 1 ELSE 0 END) as warning,
|
|
SUM(CASE WHEN level = 'danger' THEN 1 ELSE 0 END) as danger
|
|
FROM anomaly_logs
|
|
WHERE detect_type = $1
|
|
AND detected_at::timestamp >= NOW() - INTERVAL '${config.interval}'
|
|
GROUP BY time_slot
|
|
ORDER BY time_slot ASC
|
|
`, [type])
|
|
} catch (e) {
|
|
rows = []
|
|
}
|
|
|
|
// 로그 데이터를 Map으로 변환
|
|
const logMap = new Map<string, { warning: number; danger: number }>()
|
|
for (const r of rows) {
|
|
logMap.set(r.time_slot, {
|
|
warning: Number(r.warning) || 0,
|
|
danger: Number(r.danger) || 0
|
|
})
|
|
}
|
|
|
|
// 전체 시간 슬롯에 데이터 매핑 (없으면 0)
|
|
const data = timeSlots.map(slot => ({
|
|
time: slot.label,
|
|
warning: logMap.get(slot.key)?.warning || 0,
|
|
danger: logMap.get(slot.key)?.danger || 0
|
|
}))
|
|
|
|
return {
|
|
data,
|
|
type,
|
|
period,
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
})
|
|
|
|
interface PeriodConfig {
|
|
interval: string
|
|
stepMinutes: number
|
|
dbFormat: string
|
|
labelFormat: (date: Date) => string
|
|
}
|
|
|
|
function getPeriodConfig(period: string): PeriodConfig {
|
|
switch (period) {
|
|
case '1h':
|
|
return {
|
|
interval: '1 hour',
|
|
stepMinutes: 5,
|
|
dbFormat: 'YYYY-MM-DD HH24:MI',
|
|
labelFormat: (d) => `${pad(d.getHours())}:${pad(d.getMinutes())}`
|
|
}
|
|
case '6h':
|
|
return {
|
|
interval: '6 hours',
|
|
stepMinutes: 30,
|
|
dbFormat: 'YYYY-MM-DD HH24:MI',
|
|
labelFormat: (d) => `${pad(d.getHours())}:${pad(d.getMinutes())}`
|
|
}
|
|
case '12h':
|
|
return {
|
|
interval: '12 hours',
|
|
stepMinutes: 60,
|
|
dbFormat: 'YYYY-MM-DD HH24:00',
|
|
labelFormat: (d) => `${pad(d.getHours())}:00`
|
|
}
|
|
case '24h':
|
|
return {
|
|
interval: '24 hours',
|
|
stepMinutes: 60,
|
|
dbFormat: 'YYYY-MM-DD HH24:00',
|
|
labelFormat: (d) => `${pad(d.getHours())}:00`
|
|
}
|
|
case '7d':
|
|
return {
|
|
interval: '7 days',
|
|
stepMinutes: 360, // 6시간
|
|
dbFormat: 'YYYY-MM-DD HH24:00',
|
|
labelFormat: (d) => `${pad(d.getMonth()+1)}/${pad(d.getDate())} ${pad(d.getHours())}시`
|
|
}
|
|
case '30d':
|
|
return {
|
|
interval: '30 days',
|
|
stepMinutes: 1440, // 1일
|
|
dbFormat: 'YYYY-MM-DD',
|
|
labelFormat: (d) => `${pad(d.getMonth()+1)}/${pad(d.getDate())}`
|
|
}
|
|
default:
|
|
return {
|
|
interval: '24 hours',
|
|
stepMinutes: 60,
|
|
dbFormat: 'YYYY-MM-DD HH24:00',
|
|
labelFormat: (d) => `${pad(d.getHours())}:00`
|
|
}
|
|
}
|
|
}
|
|
|
|
function generateTimeSlots(config: PeriodConfig): { key: string; label: string }[] {
|
|
const slots: { key: string; label: string }[] = []
|
|
const now = new Date()
|
|
|
|
// 현재 시간을 간격에 맞게 내림
|
|
const roundedNow = new Date(now)
|
|
if (config.stepMinutes >= 1440) {
|
|
// 일 단위: 오늘 00:00
|
|
roundedNow.setHours(0, 0, 0, 0)
|
|
} else if (config.stepMinutes >= 60) {
|
|
// 시간 단위: 현재 시간 00분
|
|
roundedNow.setMinutes(0, 0, 0)
|
|
} else {
|
|
// 분 단위: 가장 가까운 간격
|
|
const mins = roundedNow.getMinutes()
|
|
const rounded = Math.floor(mins / config.stepMinutes) * config.stepMinutes
|
|
roundedNow.setMinutes(rounded, 0, 0)
|
|
}
|
|
|
|
// interval을 밀리초로 변환
|
|
const intervalMs = parseInterval(config.interval)
|
|
const startTime = new Date(roundedNow.getTime() - intervalMs)
|
|
|
|
// 시작부터 현재까지 슬롯 생성
|
|
const stepMs = config.stepMinutes * 60 * 1000
|
|
let current = new Date(startTime)
|
|
|
|
while (current <= roundedNow) {
|
|
const key = formatDbKey(current, config.dbFormat)
|
|
const label = config.labelFormat(current)
|
|
slots.push({ key, label })
|
|
current = new Date(current.getTime() + stepMs)
|
|
}
|
|
|
|
return slots
|
|
}
|
|
|
|
function parseInterval(interval: string): number {
|
|
const match = interval.match(/(\d+)\s*(hour|hours|day|days)/)
|
|
if (!match) return 24 * 60 * 60 * 1000
|
|
|
|
const num = parseInt(match[1])
|
|
const unit = match[2]
|
|
|
|
if (unit.startsWith('day')) {
|
|
return num * 24 * 60 * 60 * 1000
|
|
} else {
|
|
return num * 60 * 60 * 1000
|
|
}
|
|
}
|
|
|
|
function formatDbKey(date: Date, format: string): string {
|
|
const y = date.getFullYear()
|
|
const m = pad(date.getMonth() + 1)
|
|
const d = pad(date.getDate())
|
|
const h = pad(date.getHours())
|
|
const mi = pad(date.getMinutes())
|
|
|
|
if (format === 'YYYY-MM-DD') {
|
|
return `${y}-${m}-${d}`
|
|
} else if (format === 'YYYY-MM-DD HH24:00') {
|
|
return `${y}-${m}-${d} ${h}:00`
|
|
} else {
|
|
return `${y}-${m}-${d} ${h}:${mi}`
|
|
}
|
|
}
|
|
|
|
function pad(n: number): string {
|
|
return n.toString().padStart(2, '0')
|
|
}
|