Files
system-monitor/backend/routes/_ws.ts
2025-12-28 14:35:41 +09:00

335 lines
9.5 KiB
TypeScript

import { query, queryOne } from '../utils/db'
interface Client {
ws: any
interval: number
timer: ReturnType<typeof setInterval> | null
autoRefresh: boolean
}
const clients = new Map<any, Client>()
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<string, Record<string, { warning: number; critical: number; danger: number }>> = {}
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<string, number> = { 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)
}
})