import { query, queryOne } from '../utils/db' interface Client { ws: any interval: number timer: ReturnType | null autoRefresh: boolean } const clients = new Map() async function getNetworkStatus() { // pubnet 상태 const pubnetStatus = await queryOne(` SELECT ps.*, pt.name as last_target_name, pt.url as last_target_url FROM pubnet_status ps LEFT JOIN pubnet_targets pt ON ps.last_target_id = pt.id WHERE ps.id = 1 `) const pubnetLogs = await query(` SELECT pl.*, pt.name as target_name, pt.url as target_url FROM pubnet_logs pl JOIN pubnet_targets pt ON pl.target_id = pt.id ORDER BY pl.checked_at DESC LIMIT 5 `) // privnet 상태 const privnetStatus = await queryOne(` SELECT ps.*, pt.name as last_target_name, pt.url as last_target_url FROM privnet_status ps LEFT JOIN privnet_targets pt ON ps.last_target_id = pt.id WHERE ps.id = 1 `) const privnetLogs = await query(` SELECT pl.*, pt.name as target_name, pt.url as target_url FROM privnet_logs pl JOIN privnet_targets pt ON pl.target_id = pt.id ORDER BY pl.checked_at DESC LIMIT 5 `) return { pubnet: { status: pubnetStatus, logs: pubnetLogs }, privnet: { status: privnetStatus, logs: privnetLogs }, timestamp: new Date().toISOString() } } async function getHistoricalData(datetime: string) { const pubnetLogs = await query(` SELECT pl.*, pt.name as target_name, pt.url as target_url FROM pubnet_logs pl JOIN pubnet_targets pt ON pl.target_id = pt.id WHERE pl.checked_at <= $1 ORDER BY pl.checked_at DESC LIMIT 5 `, [datetime]) const privnetLogs = await query(` SELECT pl.*, pt.name as target_name, pt.url as target_url FROM privnet_logs pl JOIN privnet_targets pt ON pl.target_id = pt.id WHERE pl.checked_at <= $1 ORDER BY pl.checked_at DESC LIMIT 5 `, [datetime]) const pubnetLatest = pubnetLogs[0] || null const privnetLatest = privnetLogs[0] || null return { pubnet: { status: pubnetLatest ? { is_healthy: pubnetLatest.is_success, last_checked_at: pubnetLatest.checked_at, last_target_name: pubnetLatest.target_name } : null, logs: pubnetLogs }, privnet: { status: privnetLatest ? { is_healthy: privnetLatest.is_success, last_checked_at: privnetLatest.checked_at, last_target_name: privnetLatest.target_name } : null, logs: privnetLogs }, timestamp: datetime } } // 임계값 조회 async function getThresholds() { let rows: any[] = [] try { rows = await query(`SELECT category, metric, warning, critical, danger FROM thresholds`) } catch (e) { // 테이블 없으면 빈 배열 } const result: Record> = {} for (const row of rows) { if (!result[row.category]) result[row.category] = {} result[row.category][row.metric] = { warning: row.warning, critical: row.critical, danger: row.danger } } return result } // 레벨 계산 function getLevel(value: number | null, threshold: { warning: number; critical: number; danger: number }): string { if (value === null || value === undefined) return 'normal' if (value >= threshold.danger) return 'danger' if (value >= threshold.critical) return 'critical' if (value >= threshold.warning) return 'warning' return 'normal' } // 레벨 우선순위 const levelPriority: Record = { normal: 0, warning: 1, critical: 2, danger: 3, offline: 4, stopped: 3 } function getHighestLevel(levels: string[]): string { let highest = 'normal' for (const level of levels) { if ((levelPriority[level] || 0) > (levelPriority[highest] || 0)) { highest = level } } return highest } // 서버 대시보드 데이터 async function getServerDashboard() { const thresholds = await getThresholds() const now = new Date() const offlineThreshold = 5 * 60 * 1000 // 5분 // 서버 목록 const servers = await query(` SELECT target_id, name as server_name, is_active FROM server_targets WHERE is_active = 1 ORDER BY name `) const serverStatuses: any[] = [] const summaryServers = { total: servers.length, normal: 0, warning: 0, critical: 0, danger: 0, offline: 0 } for (const server of servers) { // 최신 로그 const snapshot = await queryOne(` SELECT cpu_usage as cpu_percent, memory_usage as memory_percent, disk_usage as disk_percent, checked_at as collected_at FROM server_logs WHERE target_id = $1 AND is_success = 1 ORDER BY checked_at DESC LIMIT 1 `, [server.target_id]) // 오프라인 체크 let isOffline = true let lastCollected = null if (snapshot && snapshot.collected_at) { lastCollected = snapshot.collected_at const collectedTime = new Date(snapshot.collected_at).getTime() isOffline = (now.getTime() - collectedTime) > offlineThreshold } // 서버 레벨 계산 let serverLevel = 'offline' let cpuLevel = 'normal', memLevel = 'normal', diskLevel = 'normal' if (!isOffline && snapshot) { cpuLevel = getLevel(Number(snapshot.cpu_percent), thresholds.server?.cpu || { warning: 70, critical: 85, danger: 95 }) memLevel = getLevel(Number(snapshot.memory_percent), thresholds.server?.memory || { warning: 80, critical: 90, danger: 95 }) diskLevel = getLevel(Number(snapshot.disk_percent), thresholds.server?.disk || { warning: 80, critical: 90, danger: 95 }) serverLevel = getHighestLevel([cpuLevel, memLevel, diskLevel]) } // 요약 집계 if (serverLevel === 'offline') summaryServers.offline++ else if (serverLevel === 'danger') summaryServers.danger++ else if (serverLevel === 'critical') summaryServers.critical++ else if (serverLevel === 'warning') summaryServers.warning++ else summaryServers.normal++ serverStatuses.push({ target_id: server.target_id, server_name: server.server_name, level: serverLevel, cpu_percent: snapshot?.cpu_percent ?? null, cpu_level: cpuLevel, memory_percent: snapshot?.memory_percent ?? null, memory_level: memLevel, disk_percent: snapshot?.disk_percent ?? null, disk_level: diskLevel, last_collected: lastCollected }) } // 서버 정렬: 이름 순 serverStatuses.sort((a, b) => a.server_name.localeCompare(b.server_name)) return { summary: { servers: summaryServers }, servers: serverStatuses, timestamp: new Date().toISOString() } } function startAutoRefresh(client: Client) { if (client.timer) { clearInterval(client.timer) } if (client.autoRefresh) { client.timer = setInterval(async () => { const data = await getNetworkStatus() client.ws.send(JSON.stringify({ type: 'status', data })) const serverData = await getServerDashboard() client.ws.send(JSON.stringify({ type: 'server', data: serverData })) }, client.interval) } } export default defineWebSocketHandler({ async open(peer) { console.log('[WebSocket] Client connected') const client: Client = { ws: peer, interval: 60 * 1000, timer: null, autoRefresh: true } clients.set(peer, client) // 초기 데이터 전송 const data = await getNetworkStatus() peer.send(JSON.stringify({ type: 'status', data })) const serverData = await getServerDashboard() peer.send(JSON.stringify({ type: 'server', data: serverData })) startAutoRefresh(client) }, async message(peer, message) { const client = clients.get(peer) if (!client) return try { const msg = JSON.parse(message.text()) switch (msg.type) { case 'set_interval': client.interval = msg.interval * 60 * 1000 console.log(`[WebSocket] Interval changed to ${msg.interval} min`) startAutoRefresh(client) break case 'set_auto_refresh': client.autoRefresh = msg.enabled console.log(`[WebSocket] Auto refresh: ${msg.enabled}`) if (msg.enabled) { startAutoRefresh(client) } else if (client.timer) { clearInterval(client.timer) client.timer = null } break case 'fetch_at': const historicalData = await getHistoricalData(msg.datetime) peer.send(JSON.stringify({ type: 'historical', data: historicalData })) break case 'refresh': const currentData = await getNetworkStatus() peer.send(JSON.stringify({ type: 'status', data: currentData })) const currentServerData = await getServerDashboard() peer.send(JSON.stringify({ type: 'server', data: currentServerData })) break case 'refresh_server': const serverDashData = await getServerDashboard() peer.send(JSON.stringify({ type: 'server', data: serverDashData })) break } } catch (err) { console.error('[WebSocket] Message parse error:', err) } }, close(peer) { console.log('[WebSocket] Client disconnected') const client = clients.get(peer) if (client?.timer) { clearInterval(client.timer) } clients.delete(peer) }, error(peer, error) { console.error('[WebSocket] Error:', error) } })