소스 수정
This commit is contained in:
@@ -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; }
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user