소스 수정

This commit is contained in:
2025-12-28 18:24:58 +09:00
parent e49c962cee
commit 240e096bd8
3 changed files with 98 additions and 11 deletions

View File

@@ -176,16 +176,19 @@ async function getServerDashboard() {
for (const server of servers) {
// 최신 스냅샷
const snapshot = await queryOne(`
SELECT cpu_percent, memory_percent, collected_at
SELECT cpu_percent, memory_percent, memory_total, memory_free, memory_used,
cpu_temp, load_percent, uptime_str, collected_at
FROM server_snapshots
WHERE target_id = $1 AND is_online = 1
ORDER BY collected_at DESC
LIMIT 1
`, [server.target_id])
// 최신 디스크 사용률 (최대값)
// 최신 디스크 사용률 (최대값) + 용량
const diskData = await queryOne(`
SELECT MAX(disk_percent) as disk_percent
SELECT MAX(disk_percent) as disk_percent,
SUM(disk_used) as disk_used,
SUM(disk_total) as disk_total
FROM server_disks
WHERE target_id = $1
AND collected_at = (SELECT MAX(collected_at) FROM server_disks WHERE target_id = $1)
@@ -291,8 +294,16 @@ async function getServerDashboard() {
cpu_level: cpuLevel,
memory_percent: snapshot?.memory_percent ?? null,
memory_level: memLevel,
memory_total: snapshot?.memory_total ?? null,
memory_free: snapshot?.memory_free ?? null,
memory_used: snapshot?.memory_used ?? null,
disk_percent: diskData?.disk_percent ?? null,
disk_level: diskLevel,
disk_used: diskData?.disk_used ?? null,
disk_total: diskData?.disk_total ?? null,
cpu_temp: snapshot?.cpu_temp ?? null,
load_percent: snapshot?.load_percent ?? null,
uptime_str: snapshot?.uptime_str ?? null,
last_collected: lastCollected,
containers: containers,
container_summary: containerSummary

View File

@@ -40,22 +40,28 @@
</div>
<template v-if="server.level !== 'offline'">
<!-- 업타임 -->
<div class="extra-row">
<span class="extra-icon"></span>
<span class="extra-value">{{ server.uptime_str || '-' }}</span>
</div>
<div class="metric-row">
<span class="metric-label">CPU</span>
<div class="progress-bar">
<div :class="['progress-fill', server.cpu_level]" :style="{ width: (server.cpu_percent || 0) + '%' }"></div>
</div>
<span :class="['metric-value', server.cpu_level, { 'value-changed': isChanged(server.target_id, 'cpu') }]">
{{ server.cpu_percent?.toFixed(0) || '-' }}
{{ server.cpu_percent?.toFixed(0) || '-' }}%
</span>
</div>
<div class="metric-row">
<span class="metric-label">MEM</span>
<div class="progress-bar">
<div :class="['progress-fill', server.memory_level]" :style="{ width: (server.memory_percent || 0) + '%' }"></div>
<div :class="['progress-fill', server.memory_level]" :style="{ width: calcMemPercent(server) + '%' }"></div>
</div>
<span :class="['metric-value', server.memory_level, { 'value-changed': isChanged(server.target_id, 'mem') }]">
{{ server.memory_percent?.toFixed(0) || '-' }}
{{ calcMemPercent(server).toFixed(0) }}%
</span>
</div>
<div class="metric-row">
@@ -64,9 +70,27 @@
<div :class="['progress-fill', server.disk_level]" :style="{ width: (server.disk_percent || 0) + '%' }"></div>
</div>
<span :class="['metric-value', server.disk_level, { 'value-changed': isChanged(server.target_id, 'disk') }]">
{{ server.disk_percent?.toFixed(0) || '-' }}
{{ server.disk_percent?.toFixed(0) || '-' }}%
</span>
</div>
<!-- 추가 정보: 온도, Load, 메모리용량, 디스크용량 -->
<div class="extra-row">
<span class="extra-icon">🌡</span>
<span class="extra-value">{{ server.cpu_temp ? server.cpu_temp + '°C' : '-' }}</span>
</div>
<div class="extra-row">
<span class="extra-icon"></span>
<span class="extra-value">{{ server.load_percent ? server.load_percent.toFixed(1) + '%' : '-' }}</span>
</div>
<div class="extra-row">
<span class="extra-icon">🔲</span>
<span class="extra-value">{{ formatServerMem(server) }}</span>
</div>
<div class="extra-row">
<span class="extra-icon">📀</span>
<span class="extra-value">{{ formatServerDisk(server) }}</span>
</div>
</template>
<template v-else>
@@ -162,8 +186,16 @@ interface ServerStatus {
cpu_level: string
memory_percent: number | null
memory_level: string
memory_total: number | null
memory_free: number | null
memory_used: number | null
disk_percent: number | null
disk_level: string
disk_used: number | null
disk_total: number | null
cpu_temp: number | null
load_percent: number | null
uptime_str: string | null
last_collected: string | null
containers: ContainerStatus[]
container_summary: { total: number; normal: number; warning: number; critical: number; stopped: number }
@@ -265,6 +297,35 @@ function isContainerChanged(serverId: number, containerName: string, metric: str
return changedKeys.value.has(`container-${serverId}-${containerName}-${metric}`)
}
// 서버 메모리 퍼센트 계산: (total - free) / total * 100
function calcMemPercent(server: ServerStatus): number {
const total = Number(server.memory_total) || 0
const free = Number(server.memory_free) || 0
if (total === 0) return 0
return ((total - free) / total) * 100
}
// 서버 메모리 용량 포맷: used/total GB
function formatServerMem(server: ServerStatus): string {
const total = Number(server.memory_total) || 0
const free = Number(server.memory_free) || 0
if (total === 0) return '-'
const used = total - free
const usedGB = (used / (1024 * 1024 * 1024)).toFixed(1)
const totalGB = (total / (1024 * 1024 * 1024)).toFixed(1)
return `${usedGB}/${totalGB}G`
}
// 서버 디스크 용량 포맷: used/total GB
function formatServerDisk(server: ServerStatus): string {
const used = Number(server.disk_used) || 0
const total = Number(server.disk_total) || 0
if (total === 0) return '-'
const usedGB = (used / (1024 * 1024 * 1024)).toFixed(0)
const totalGB = (total / (1024 * 1024 * 1024)).toFixed(0)
return `${usedGB}/${totalGB}G`
}
function sortContainers(containers: ContainerStatus[]) {
return [...containers].sort((a, b) => a.name.localeCompare(b.name))
}
@@ -378,12 +439,16 @@ function formatTimeAgo(datetime: string | null): string {
.progress-fill.warning { background: #eab308; }
.progress-fill.critical { background: #f97316; }
.progress-fill.danger { background: #ef4444; }
.metric-value { font-size: 17px; font-weight: 700; width: 36px; text-align: right; transition: all 0.3s; padding: 2px 4px; border-radius: 4px; }
.metric-value { font-size: 17px; font-weight: 700; width: 44px; text-align: right; transition: all 0.3s; padding: 2px 4px; border-radius: 4px; }
.metric-value.normal { color: #16a34a; }
.metric-value.warning { color: #ca8a04; }
.metric-value.critical { color: #ea580c; }
.metric-value.danger { color: #dc2626; }
.extra-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.extra-icon { font-size: 12px; width: 16px; text-align: center; }
.extra-value { font-size: 12px; font-weight: 600; color: var(--text-secondary); font-family: monospace; }
.offline-info { text-align: center; padding: 24px 0; color: var(--text-muted); }
.offline-text { font-size: 18px; margin-bottom: 8px; }
.offline-time { font-size: 15px; opacity: 0.7; }

View File

@@ -405,9 +405,19 @@ async function fetchSnapshots() {
load: validLoad.length ? (validLoad.reduce((a: number, b: number) => a + b, 0) / validLoad.length).toFixed(1) : '-'
}
// Memory/Swap 라인 차트
const memData = data.map((d: any) => d.memory_percent || 0)
const swapData = data.map((d: any) => d.swap_percent || 0)
// Memory/Swap 라인 차트 - 퍼센트 계산: (total - free) / total * 100
const memData = data.map((d: any) => {
const total = Number(d.memory_total) || 0
const free = Number(d.memory_free) || 0
if (total === 0) return 0
return ((total - free) / total) * 100
})
const swapData = data.map((d: any) => {
const total = Number(d.swap_total) || 0
const used = Number(d.swap_used) || 0
if (total === 0) return 0
return (used / total) * 100
})
memChart = createLineChart(memChartRef.value!, labels, [
{ label: 'Memory %', data: memData, borderColor: chartColors[1] },
{ label: 'Swap %', data: swapData, borderColor: chartColors[2] }
@@ -415,6 +425,7 @@ async function fetchSnapshots() {
// 평균 계산 (Memory, Swap) + 사용량/전체용량 (BigInt는 문자열로 반환되므로 Number로 변환)
// 메모리 사용량 = total - free (free가 있으면 사용, 없으면 used 사용)
// 메모리 퍼센트도 (total - free) / total * 100 으로 계산
const validMem = memData.filter((v: number) => v > 0)
const validSwap = swapData.filter((v: number) => v >= 0)
const memUsedData = data.map((d: any) => {