소스 수정
This commit is contained in:
@@ -176,16 +176,19 @@ async function getServerDashboard() {
|
|||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
// 최신 스냅샷
|
// 최신 스냅샷
|
||||||
const snapshot = await queryOne(`
|
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
|
FROM server_snapshots
|
||||||
WHERE target_id = $1 AND is_online = 1
|
WHERE target_id = $1 AND is_online = 1
|
||||||
ORDER BY collected_at DESC
|
ORDER BY collected_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`, [server.target_id])
|
`, [server.target_id])
|
||||||
|
|
||||||
// 최신 디스크 사용률 (최대값)
|
// 최신 디스크 사용률 (최대값) + 용량
|
||||||
const diskData = await queryOne(`
|
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
|
FROM server_disks
|
||||||
WHERE target_id = $1
|
WHERE target_id = $1
|
||||||
AND collected_at = (SELECT MAX(collected_at) 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,
|
cpu_level: cpuLevel,
|
||||||
memory_percent: snapshot?.memory_percent ?? null,
|
memory_percent: snapshot?.memory_percent ?? null,
|
||||||
memory_level: memLevel,
|
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_percent: diskData?.disk_percent ?? null,
|
||||||
disk_level: diskLevel,
|
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,
|
last_collected: lastCollected,
|
||||||
containers: containers,
|
containers: containers,
|
||||||
container_summary: containerSummary
|
container_summary: containerSummary
|
||||||
|
|||||||
@@ -40,22 +40,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="server.level !== 'offline'">
|
<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">
|
<div class="metric-row">
|
||||||
<span class="metric-label">CPU</span>
|
<span class="metric-label">CPU</span>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div :class="['progress-fill', server.cpu_level]" :style="{ width: (server.cpu_percent || 0) + '%' }"></div>
|
<div :class="['progress-fill', server.cpu_level]" :style="{ width: (server.cpu_percent || 0) + '%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
<span :class="['metric-value', server.cpu_level, { 'value-changed': isChanged(server.target_id, 'cpu') }]">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric-row">
|
<div class="metric-row">
|
||||||
<span class="metric-label">MEM</span>
|
<span class="metric-label">MEM</span>
|
||||||
<div class="progress-bar">
|
<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>
|
</div>
|
||||||
<span :class="['metric-value', server.memory_level, { 'value-changed': isChanged(server.target_id, 'mem') }]">
|
<span :class="['metric-value', server.memory_level, { 'value-changed': isChanged(server.target_id, 'mem') }]">
|
||||||
{{ server.memory_percent?.toFixed(0) || '-' }}
|
{{ calcMemPercent(server).toFixed(0) }}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric-row">
|
<div class="metric-row">
|
||||||
@@ -64,9 +70,27 @@
|
|||||||
<div :class="['progress-fill', server.disk_level]" :style="{ width: (server.disk_percent || 0) + '%' }"></div>
|
<div :class="['progress-fill', server.disk_level]" :style="{ width: (server.disk_percent || 0) + '%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
<span :class="['metric-value', server.disk_level, { 'value-changed': isChanged(server.target_id, 'disk') }]">
|
<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>
|
</span>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -162,8 +186,16 @@ interface ServerStatus {
|
|||||||
cpu_level: string
|
cpu_level: string
|
||||||
memory_percent: number | null
|
memory_percent: number | null
|
||||||
memory_level: string
|
memory_level: string
|
||||||
|
memory_total: number | null
|
||||||
|
memory_free: number | null
|
||||||
|
memory_used: number | null
|
||||||
disk_percent: number | null
|
disk_percent: number | null
|
||||||
disk_level: string
|
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
|
last_collected: string | null
|
||||||
containers: ContainerStatus[]
|
containers: ContainerStatus[]
|
||||||
container_summary: { total: number; normal: number; warning: number; critical: number; stopped: number }
|
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}`)
|
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[]) {
|
function sortContainers(containers: ContainerStatus[]) {
|
||||||
return [...containers].sort((a, b) => a.name.localeCompare(b.name))
|
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.warning { background: #eab308; }
|
||||||
.progress-fill.critical { background: #f97316; }
|
.progress-fill.critical { background: #f97316; }
|
||||||
.progress-fill.danger { background: #ef4444; }
|
.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.normal { color: #16a34a; }
|
||||||
.metric-value.warning { color: #ca8a04; }
|
.metric-value.warning { color: #ca8a04; }
|
||||||
.metric-value.critical { color: #ea580c; }
|
.metric-value.critical { color: #ea580c; }
|
||||||
.metric-value.danger { color: #dc2626; }
|
.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-info { text-align: center; padding: 24px 0; color: var(--text-muted); }
|
||||||
.offline-text { font-size: 18px; margin-bottom: 8px; }
|
.offline-text { font-size: 18px; margin-bottom: 8px; }
|
||||||
.offline-time { font-size: 15px; opacity: 0.7; }
|
.offline-time { font-size: 15px; opacity: 0.7; }
|
||||||
|
|||||||
@@ -405,9 +405,19 @@ async function fetchSnapshots() {
|
|||||||
load: validLoad.length ? (validLoad.reduce((a: number, b: number) => a + b, 0) / validLoad.length).toFixed(1) : '-'
|
load: validLoad.length ? (validLoad.reduce((a: number, b: number) => a + b, 0) / validLoad.length).toFixed(1) : '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memory/Swap 라인 차트
|
// Memory/Swap 라인 차트 - 퍼센트 계산: (total - free) / total * 100
|
||||||
const memData = data.map((d: any) => d.memory_percent || 0)
|
const memData = data.map((d: any) => {
|
||||||
const swapData = data.map((d: any) => d.swap_percent || 0)
|
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, [
|
memChart = createLineChart(memChartRef.value!, labels, [
|
||||||
{ label: 'Memory %', data: memData, borderColor: chartColors[1] },
|
{ label: 'Memory %', data: memData, borderColor: chartColors[1] },
|
||||||
{ label: 'Swap %', data: swapData, borderColor: chartColors[2] }
|
{ label: 'Swap %', data: swapData, borderColor: chartColors[2] }
|
||||||
@@ -415,6 +425,7 @@ async function fetchSnapshots() {
|
|||||||
|
|
||||||
// 평균 계산 (Memory, Swap) + 사용량/전체용량 (BigInt는 문자열로 반환되므로 Number로 변환)
|
// 평균 계산 (Memory, Swap) + 사용량/전체용량 (BigInt는 문자열로 반환되므로 Number로 변환)
|
||||||
// 메모리 사용량 = total - free (free가 있으면 사용, 없으면 used 사용)
|
// 메모리 사용량 = total - free (free가 있으면 사용, 없으면 used 사용)
|
||||||
|
// 메모리 퍼센트도 (total - free) / total * 100 으로 계산
|
||||||
const validMem = memData.filter((v: number) => v > 0)
|
const validMem = memData.filter((v: number) => v > 0)
|
||||||
const validSwap = swapData.filter((v: number) => v >= 0)
|
const validSwap = swapData.filter((v: number) => v >= 0)
|
||||||
const memUsedData = data.map((d: any) => {
|
const memUsedData = data.map((d: any) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user