From 1fc6a9ccd911e3d2709e6d9266e42cae05f7eed6 Mon Sep 17 00:00:00 2001 From: Hyoseong Jo Date: Sun, 28 Dec 2025 16:27:46 +0900 Subject: [PATCH] =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/server/history/containers.get.ts | 2 +- backend/api/server/history/disks.get.ts | 2 +- backend/api/server/history/networks.get.ts | 2 +- backend/routes/_ws.ts | 71 +++++++++++++++++++- frontend/components/SidebarNav.vue | 12 ++-- frontend/settings/thresholds.vue | 15 ++++- 6 files changed, 89 insertions(+), 15 deletions(-) diff --git a/backend/api/server/history/containers.get.ts b/backend/api/server/history/containers.get.ts index 8332829..e7cb705 100644 --- a/backend/api/server/history/containers.get.ts +++ b/backend/api/server/history/containers.get.ts @@ -45,7 +45,7 @@ export default defineEventHandler(async (event) => { collected_at FROM server_containers WHERE target_id = $1 - AND collected_at >= NOW() - INTERVAL '${interval}' + AND collected_at::timestamp >= NOW() - INTERVAL '${interval}' ORDER BY collected_at ASC, container_name ASC `, [targetId]) } catch (e) { diff --git a/backend/api/server/history/disks.get.ts b/backend/api/server/history/disks.get.ts index 029541e..da7a6b7 100644 --- a/backend/api/server/history/disks.get.ts +++ b/backend/api/server/history/disks.get.ts @@ -42,7 +42,7 @@ export default defineEventHandler(async (event) => { collected_at FROM server_disks WHERE target_id = $1 - AND collected_at >= NOW() - INTERVAL '${interval}' + AND collected_at::timestamp >= NOW() - INTERVAL '${interval}' ORDER BY collected_at ASC, mount_point ASC `, [targetId]) } catch (e) { diff --git a/backend/api/server/history/networks.get.ts b/backend/api/server/history/networks.get.ts index c47f7c0..7bd2ea6 100644 --- a/backend/api/server/history/networks.get.ts +++ b/backend/api/server/history/networks.get.ts @@ -42,7 +42,7 @@ export default defineEventHandler(async (event) => { collected_at FROM server_networks WHERE target_id = $1 - AND collected_at >= NOW() - INTERVAL '${interval}' + AND collected_at::timestamp >= NOW() - INTERVAL '${interval}' ORDER BY collected_at ASC, interface_name ASC `, [targetId]) } catch (e) { diff --git a/backend/routes/_ws.ts b/backend/routes/_ws.ts index 1a7710f..66b277b 100644 --- a/backend/routes/_ws.ts +++ b/backend/routes/_ws.ts @@ -171,9 +171,10 @@ async function getServerDashboard() { 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 = await queryOne(` SELECT cpu_percent, memory_percent, collected_at FROM server_snapshots @@ -209,6 +210,68 @@ async function getServerDashboard() { else if (serverLevel === 'warning') summaryServers.warning++ else summaryServers.normal++ + // 컨테이너 조회 (최신 데이터만) + const containers: any[] = [] + const containerSummary = { total: 0, normal: 0, warning: 0, critical: 0, stopped: 0 } + + if (!isOffline) { + // 서버별 최신 컨테이너 (container_name별 최신 1건) + const latestContainers = await query(` + SELECT DISTINCT ON (container_name) + container_name as name, + container_status as status, + cpu_percent, + memory_usage, + memory_limit, + uptime, + network_rx, + network_tx + FROM server_containers + WHERE target_id = $1 + ORDER BY container_name, collected_at DESC + `, [server.target_id]) + + for (const c of latestContainers) { + let containerLevel = 'normal' + + if (c.status !== 'running') { + containerLevel = 'stopped' + containerSummary.stopped++ + } else { + const cCpuLevel = getLevel(Number(c.cpu_percent), thresholds.container?.cpu || { warning: 80, critical: 90, danger: 95 }) + const memPct = c.memory_limit ? (Number(c.memory_usage) / Number(c.memory_limit)) * 100 : 0 + const cMemLevel = getLevel(memPct, thresholds.container?.memory || { warning: 80, critical: 90, danger: 95 }) + containerLevel = getHighestLevel([cCpuLevel, cMemLevel]) + + if (containerLevel === 'danger') containerSummary.critical++ + else if (containerLevel === 'critical') containerSummary.critical++ + else if (containerLevel === 'warning') containerSummary.warning++ + else containerSummary.normal++ + } + + containers.push({ + name: c.name, + status: c.status || 'unknown', + 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 + }) + + containerSummary.total++ + summaryContainers.total++ + } + + // 전체 컨테이너 요약 집계 + summaryContainers.normal += containerSummary.normal + summaryContainers.warning += containerSummary.warning + summaryContainers.critical += containerSummary.critical + summaryContainers.stopped += containerSummary.stopped + } + serverStatuses.push({ target_id: server.target_id, server_name: server.server_name, @@ -219,7 +282,9 @@ async function getServerDashboard() { memory_level: memLevel, disk_percent: snapshot?.disk_percent ?? null, disk_level: diskLevel, - last_collected: lastCollected + last_collected: lastCollected, + containers: containers, + container_summary: containerSummary }) } @@ -227,7 +292,7 @@ async function getServerDashboard() { serverStatuses.sort((a, b) => a.server_name.localeCompare(b.server_name)) return { - summary: { servers: summaryServers }, + summary: { servers: summaryServers, containers: summaryContainers }, servers: serverStatuses, timestamp: new Date().toISOString() } diff --git a/frontend/components/SidebarNav.vue b/frontend/components/SidebarNav.vue index d2806e6..016a9ea 100644 --- a/frontend/components/SidebarNav.vue +++ b/frontend/components/SidebarNav.vue @@ -37,6 +37,11 @@ Server Status + + ⚙️ + Thresholds + + @@ -58,13 +63,6 @@ 📉 추세 분석 - - - - - ⚙️ - 임계값 설정 - diff --git a/frontend/settings/thresholds.vue b/frontend/settings/thresholds.vue index 8552c9a..3291a8e 100644 --- a/frontend/settings/thresholds.vue +++ b/frontend/settings/thresholds.vue @@ -138,9 +138,20 @@ const thresholds = ref(JSON.parse(JSON.stringify(defaultThresholds))) async function fetchThresholds() { try { - const data = await $fetch('/api/settings/thresholds') + const data = await $fetch('/api/settings/thresholds') as any if (data) { - thresholds.value = data as typeof defaultThresholds + // 기본값과 병합 (API 응답이 불완전할 수 있음) + thresholds.value = { + server: { + cpu: { ...defaultThresholds.server.cpu, ...data.server?.cpu }, + memory: { ...defaultThresholds.server.memory, ...data.server?.memory }, + disk: { ...defaultThresholds.server.disk, ...data.server?.disk } + }, + container: { + cpu: { ...defaultThresholds.container.cpu, ...data.container?.cpu }, + memory: { ...defaultThresholds.container.memory, ...data.container?.memory } + } + } } } catch (err) { console.error('Failed to fetch thresholds:', err)