diff --git a/backend/api/server/targets/[id].delete.ts b/backend/api/server/targets/[id].delete.ts index 531f5a0..7cdbdeb 100644 --- a/backend/api/server/targets/[id].delete.ts +++ b/backend/api/server/targets/[id].delete.ts @@ -1,4 +1,4 @@ -import { getDb } from '../../../utils/db' +import { execute } from '../../../utils/db' import { refreshServerTimer } from '../../../utils/server-scheduler' export default defineEventHandler(async (event) => { @@ -14,14 +14,13 @@ export default defineEventHandler(async (event) => { // 스케줄러에서 제거 refreshServerTimer(Number(targetId)) - const db = getDb() - - const result = db.prepare(` - DELETE FROM server_targets WHERE target_id = ? - `).run(targetId) + const changes = await execute( + 'DELETE FROM server_targets WHERE target_id = $1', + [targetId] + ) return { success: true, - changes: result.changes + changes } }) diff --git a/backend/api/server/targets/[id].put.ts b/backend/api/server/targets/[id].put.ts index 24bac81..d2ed623 100644 --- a/backend/api/server/targets/[id].put.ts +++ b/backend/api/server/targets/[id].put.ts @@ -1,10 +1,10 @@ -import { getDb } from '../../../utils/db' +import { execute } from '../../../utils/db' import { refreshServerTimer } from '../../../utils/server-scheduler' export default defineEventHandler(async (event) => { const targetId = getRouterParam(event, 'id') const body = await readBody(event) - const { server_name, server_ip, glances_url, is_active, collect_interval } = body + const { name, host, port, username, auth_type, is_active } = body if (!targetId) { throw createError({ @@ -13,24 +13,23 @@ export default defineEventHandler(async (event) => { }) } - const db = getDb() - - const result = db.prepare(` + const changes = await execute(` UPDATE server_targets - SET server_name = ?, - server_ip = ?, - glances_url = ?, - is_active = ?, - collect_interval = ?, - updated_at = datetime('now', 'localtime') - WHERE target_id = ? - `).run(server_name, server_ip, glances_url, is_active ? 1 : 0, collect_interval || 60, targetId) + SET name = $1, + host = $2, + port = $3, + username = $4, + auth_type = $5, + is_active = $6, + updated_at = NOW() + WHERE target_id = $7 + `, [name, host, port || 22, username, auth_type || 'password', is_active ? 1 : 0, targetId]) // 스케줄러에 반영 refreshServerTimer(Number(targetId)) return { success: true, - changes: result.changes + changes } }) diff --git a/backend/routes/_ws.ts b/backend/routes/_ws.ts index dc52aa8..829b731 100644 --- a/backend/routes/_ws.ts +++ b/backend/routes/_ws.ts @@ -1,4 +1,4 @@ -import { getDb } from '../utils/db' +import { query, queryOne } from '../utils/db' interface Client { ws: any @@ -9,11 +9,9 @@ interface Client { const clients = new Map() -function getNetworkStatus() { - const db = getDb() - +async function getNetworkStatus() { // pubnet 상태 - const pubnetStatus = db.prepare(` + const pubnetStatus = await queryOne(` SELECT ps.*, pt.name as last_target_name, @@ -21,9 +19,9 @@ function getNetworkStatus() { FROM pubnet_status ps LEFT JOIN pubnet_targets pt ON ps.last_target_id = pt.id WHERE ps.id = 1 - `).get() + `) - const pubnetLogs = db.prepare(` + const pubnetLogs = await query(` SELECT pl.*, pt.name as target_name, @@ -32,10 +30,10 @@ function getNetworkStatus() { JOIN pubnet_targets pt ON pl.target_id = pt.id ORDER BY pl.checked_at DESC LIMIT 5 - `).all() + `) // privnet 상태 - const privnetStatus = db.prepare(` + const privnetStatus = await queryOne(` SELECT ps.*, pt.name as last_target_name, @@ -43,9 +41,9 @@ function getNetworkStatus() { FROM privnet_status ps LEFT JOIN privnet_targets pt ON ps.last_target_id = pt.id WHERE ps.id = 1 - `).get() + `) - const privnetLogs = db.prepare(` + const privnetLogs = await query(` SELECT pl.*, pt.name as target_name, @@ -54,7 +52,7 @@ function getNetworkStatus() { JOIN privnet_targets pt ON pl.target_id = pt.id ORDER BY pl.checked_at DESC LIMIT 5 - `).all() + `) return { pubnet: { @@ -69,35 +67,31 @@ function getNetworkStatus() { } } -function getHistoricalData(datetime: string) { - const db = getDb() - - // 특정 시간 이전의 로그 조회 - const pubnetLogs = db.prepare(` +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 <= @datetime + WHERE pl.checked_at <= $1 ORDER BY pl.checked_at DESC LIMIT 5 - `).all({ datetime }) + `, [datetime]) - const privnetLogs = db.prepare(` + 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 <= @datetime + WHERE pl.checked_at <= $1 ORDER BY pl.checked_at DESC LIMIT 5 - `).all({ datetime }) + `, [datetime]) - // 해당 시점의 최신 상태 (로그 기준) const pubnetLatest = pubnetLogs[0] || null const privnetLatest = privnetLogs[0] || null @@ -123,9 +117,13 @@ function getHistoricalData(datetime: string) { } // 임계값 조회 -function getThresholds() { - const db = getDb() - const rows = db.prepare(`SELECT category, metric, warning, critical, danger FROM thresholds`).all() as any[] +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) { @@ -158,46 +156,38 @@ function getHighestLevel(levels: string[]): string { } // 서버 대시보드 데이터 -function getServerDashboard() { - const db = getDb() - const thresholds = getThresholds() +async function getServerDashboard() { + const thresholds = await getThresholds() const now = new Date() const offlineThreshold = 5 * 60 * 1000 // 5분 // 서버 목록 - const servers = db.prepare(`SELECT target_id, server_name, is_active FROM server_targets WHERE is_active = 1 ORDER BY server_name`).all() as any[] + 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 } - const summaryContainers = { total: 0, normal: 0, warning: 0, critical: 0, danger: 0, stopped: 0 } for (const server of servers) { - // 최신 스냅샷 - const snapshot = db.prepare(` - SELECT cpu_percent, memory_percent, collected_at - FROM server_snapshots - WHERE target_id = ? - ORDER BY collected_at DESC + // 최신 로그 + 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 - `).get(server.target_id) as any - - // 디스크 정보 조회 (루트 마운트 또는 최대 사용률) - const disk = db.prepare(` - SELECT disk_percent - FROM server_disks - WHERE target_id = ? - ORDER BY - CASE WHEN mount_point = '/' THEN 0 ELSE 1 END, - disk_percent DESC - LIMIT 1 - `).get(server.target_id) as any + `, [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.replace(' ', 'T') + '+09:00').getTime() + const collectedTime = new Date(snapshot.collected_at).getTime() isOffline = (now.getTime() - collectedTime) > offlineThreshold } @@ -206,65 +196,13 @@ function getServerDashboard() { let cpuLevel = 'normal', memLevel = 'normal', diskLevel = 'normal' if (!isOffline && snapshot) { - cpuLevel = getLevel(snapshot.cpu_percent, thresholds.server?.cpu || { warning: 70, critical: 85, danger: 95 }) - memLevel = getLevel(snapshot.memory_percent, thresholds.server?.memory || { warning: 80, critical: 90, danger: 95 }) - diskLevel = getLevel(disk?.disk_percent ?? null, thresholds.server?.disk || { warning: 80, critical: 90, danger: 95 }) + 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]) } - // 컨테이너 조회 (최신 데이터, 중복 제거) - const containers = db.prepare(` - SELECT container_name, container_status, cpu_percent, memory_usage, memory_limit, uptime, network_rx, network_tx - FROM server_containers - WHERE target_id = ? AND collected_at = ( - SELECT MAX(collected_at) FROM server_containers WHERE target_id = ? - ) - GROUP BY container_name - ORDER BY container_name - `).all(server.target_id, server.target_id) as any[] - - const containerStatuses: any[] = [] - const containerSummary = { total: containers.length, normal: 0, warning: 0, critical: 0, danger: 0, stopped: 0 } - - for (const c of containers) { - let containerLevel = 'normal' - - if (c.container_status !== 'running') { - containerLevel = 'stopped' - containerSummary.stopped++ - } else { - const cCpuLevel = getLevel(c.cpu_percent, thresholds.container?.cpu || { warning: 80, critical: 90, danger: 95 }) - const cMemPercent = c.memory_limit ? (c.memory_usage / c.memory_limit * 100) : null - const cMemLevel = getLevel(cMemPercent, thresholds.container?.memory || { warning: 80, critical: 90, danger: 95 }) - containerLevel = getHighestLevel([cCpuLevel, cMemLevel]) - - if (containerLevel === 'normal') containerSummary.normal++ - else if (containerLevel === 'warning') containerSummary.warning++ - else if (containerLevel === 'critical') containerSummary.critical++ - else if (containerLevel === 'danger') containerSummary.danger++ - } - - containerStatuses.push({ - name: c.container_name, - status: c.container_status, - level: containerLevel, - cpu_percent: c.cpu_percent, - memory_usage: c.memory_usage, - memory_limit: c.memory_limit, - uptime: c.uptime, - network_rx: c.network_rx, - network_tx: c.network_tx - }) - } - // 요약 집계 - summaryContainers.total += containerSummary.total - summaryContainers.normal += containerSummary.normal - summaryContainers.warning += containerSummary.warning - summaryContainers.critical += containerSummary.critical - summaryContainers.danger += containerSummary.danger - summaryContainers.stopped += containerSummary.stopped - if (serverLevel === 'offline') summaryServers.offline++ else if (serverLevel === 'danger') summaryServers.danger++ else if (serverLevel === 'critical') summaryServers.critical++ @@ -279,23 +217,17 @@ function getServerDashboard() { cpu_level: cpuLevel, memory_percent: snapshot?.memory_percent ?? null, memory_level: memLevel, - disk_percent: disk?.disk_percent ?? null, + disk_percent: snapshot?.disk_percent ?? null, disk_level: diskLevel, - last_collected: lastCollected, - containers: containerStatuses, - container_summary: containerSummary + last_collected: lastCollected }) } - // 서버 정렬: 컨테이너 많은 순 → 이름 순 - serverStatuses.sort((a, b) => { - const containerDiff = (b.container_summary?.total || 0) - (a.container_summary?.total || 0) - if (containerDiff !== 0) return containerDiff - return a.server_name.localeCompare(b.server_name) - }) + // 서버 정렬: 이름 순 + serverStatuses.sort((a, b) => a.server_name.localeCompare(b.server_name)) return { - summary: { servers: summaryServers, containers: summaryContainers }, + summary: { servers: summaryServers }, servers: serverStatuses, timestamp: new Date().toISOString() } @@ -307,24 +239,23 @@ function startAutoRefresh(client: Client) { } if (client.autoRefresh) { - client.timer = setInterval(() => { - const data = getNetworkStatus() + client.timer = setInterval(async () => { + const data = await getNetworkStatus() client.ws.send(JSON.stringify({ type: 'status', data })) - // 서버 대시보드 데이터도 전송 - const serverData = getServerDashboard() + const serverData = await getServerDashboard() client.ws.send(JSON.stringify({ type: 'server', data: serverData })) }, client.interval) } } export default defineWebSocketHandler({ - open(peer) { + async open(peer) { console.log('[WebSocket] Client connected') const client: Client = { ws: peer, - interval: 60 * 1000, // 기본 1분 + interval: 60 * 1000, timer: null, autoRefresh: true } @@ -332,18 +263,16 @@ export default defineWebSocketHandler({ clients.set(peer, client) // 초기 데이터 전송 - const data = getNetworkStatus() + const data = await getNetworkStatus() peer.send(JSON.stringify({ type: 'status', data })) - // 서버 대시보드 데이터 전송 - const serverData = getServerDashboard() + const serverData = await getServerDashboard() peer.send(JSON.stringify({ type: 'server', data: serverData })) - // 자동 갱신 시작 startAutoRefresh(client) }, - message(peer, message) { + async message(peer, message) { const client = clients.get(peer) if (!client) return @@ -352,14 +281,12 @@ export default defineWebSocketHandler({ 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': - // 자동 갱신 ON/OFF client.autoRefresh = msg.enabled console.log(`[WebSocket] Auto refresh: ${msg.enabled}`) if (msg.enabled) { @@ -371,23 +298,19 @@ export default defineWebSocketHandler({ break case 'fetch_at': - // 특정 시간 데이터 조회 - const historicalData = getHistoricalData(msg.datetime) + const historicalData = await getHistoricalData(msg.datetime) peer.send(JSON.stringify({ type: 'historical', data: historicalData })) break case 'refresh': - // 즉시 갱신 요청 - const currentData = getNetworkStatus() + const currentData = await getNetworkStatus() peer.send(JSON.stringify({ type: 'status', data: currentData })) - // 서버 데이터도 전송 - const currentServerData = getServerDashboard() + const currentServerData = await getServerDashboard() peer.send(JSON.stringify({ type: 'server', data: currentServerData })) break case 'refresh_server': - // 서버 대시보드만 즉시 갱신 - const serverDashData = getServerDashboard() + const serverDashData = await getServerDashboard() peer.send(JSON.stringify({ type: 'server', data: serverDashData })) break }