소스 수정
This commit is contained in:
@@ -210,9 +210,17 @@ async function getServerDashboard() {
|
|||||||
let serverLevel = 'offline'
|
let serverLevel = 'offline'
|
||||||
let cpuLevel = 'normal', memLevel = 'normal', diskLevel = 'normal'
|
let cpuLevel = 'normal', memLevel = 'normal', diskLevel = 'normal'
|
||||||
|
|
||||||
|
// 메모리 퍼센트 계산: (total - free) / total * 100
|
||||||
|
let calculatedMemPercent = 0
|
||||||
|
if (snapshot) {
|
||||||
|
const memTotal = Number(snapshot.memory_total) || 0
|
||||||
|
const memFree = Number(snapshot.memory_free) || 0
|
||||||
|
calculatedMemPercent = memTotal > 0 ? ((memTotal - memFree) / memTotal) * 100 : 0
|
||||||
|
}
|
||||||
|
|
||||||
if (!isOffline && snapshot) {
|
if (!isOffline && snapshot) {
|
||||||
cpuLevel = getLevel(Number(snapshot.cpu_percent), thresholds.server?.cpu || { warning: 70, critical: 85, 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 })
|
memLevel = getLevel(calculatedMemPercent, thresholds.server?.memory || { warning: 80, critical: 90, danger: 95 })
|
||||||
diskLevel = getLevel(Number(diskData?.disk_percent), thresholds.server?.disk || { warning: 80, critical: 90, danger: 95 })
|
diskLevel = getLevel(Number(diskData?.disk_percent), thresholds.server?.disk || { warning: 80, critical: 90, danger: 95 })
|
||||||
serverLevel = getHighestLevel([cpuLevel, memLevel, diskLevel])
|
serverLevel = getHighestLevel([cpuLevel, memLevel, diskLevel])
|
||||||
}
|
}
|
||||||
@@ -292,7 +300,7 @@ async function getServerDashboard() {
|
|||||||
level: serverLevel,
|
level: serverLevel,
|
||||||
cpu_percent: snapshot?.cpu_percent ?? null,
|
cpu_percent: snapshot?.cpu_percent ?? null,
|
||||||
cpu_level: cpuLevel,
|
cpu_level: cpuLevel,
|
||||||
memory_percent: snapshot?.memory_percent ?? null,
|
memory_percent: calculatedMemPercent,
|
||||||
memory_level: memLevel,
|
memory_level: memLevel,
|
||||||
memory_total: snapshot?.memory_total ?? null,
|
memory_total: snapshot?.memory_total ?? null,
|
||||||
memory_free: snapshot?.memory_free ?? null,
|
memory_free: snapshot?.memory_free ?? null,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ async function detectAnomalies(targetId: number, serverName: string) {
|
|||||||
const SHORT_TERM_THRESHOLD = 30
|
const SHORT_TERM_THRESHOLD = 30
|
||||||
|
|
||||||
const snapshots = await query<any>(`
|
const snapshots = await query<any>(`
|
||||||
SELECT cpu_percent, memory_percent
|
SELECT cpu_percent, memory_total, memory_free
|
||||||
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
|
||||||
@@ -84,8 +84,15 @@ async function detectAnomalies(targetId: number, serverName: string) {
|
|||||||
|
|
||||||
const currCpuAvg = currSnapshots.reduce((sum, s) => sum + (s.cpu_percent || 0), 0) / currSnapshots.length
|
const currCpuAvg = currSnapshots.reduce((sum, s) => sum + (s.cpu_percent || 0), 0) / currSnapshots.length
|
||||||
const prevCpuAvg = prevSnapshots.reduce((sum, s) => sum + (s.cpu_percent || 0), 0) / prevSnapshots.length
|
const prevCpuAvg = prevSnapshots.reduce((sum, s) => sum + (s.cpu_percent || 0), 0) / prevSnapshots.length
|
||||||
const currMemAvg = currSnapshots.reduce((sum, s) => sum + (s.memory_percent || 0), 0) / currSnapshots.length
|
|
||||||
const prevMemAvg = prevSnapshots.reduce((sum, s) => sum + (s.memory_percent || 0), 0) / prevSnapshots.length
|
// 메모리: (total - free) / total * 100
|
||||||
|
const calcMemPct = (s: any) => {
|
||||||
|
const total = Number(s.memory_total) || 0
|
||||||
|
const free = Number(s.memory_free) || 0
|
||||||
|
return total > 0 ? ((total - free) / total) * 100 : 0
|
||||||
|
}
|
||||||
|
const currMemAvg = currSnapshots.reduce((sum, s) => sum + calcMemPct(s), 0) / currSnapshots.length
|
||||||
|
const prevMemAvg = prevSnapshots.reduce((sum, s) => sum + calcMemPct(s), 0) / prevSnapshots.length
|
||||||
|
|
||||||
const cpuChange = prevCpuAvg > 1 ? ((currCpuAvg - prevCpuAvg) / prevCpuAvg) * 100 : currCpuAvg - prevCpuAvg
|
const cpuChange = prevCpuAvg > 1 ? ((currCpuAvg - prevCpuAvg) / prevCpuAvg) * 100 : currCpuAvg - prevCpuAvg
|
||||||
const memChange = prevMemAvg > 1 ? ((currMemAvg - prevMemAvg) / prevMemAvg) * 100 : currMemAvg - prevMemAvg
|
const memChange = prevMemAvg > 1 ? ((currMemAvg - prevMemAvg) / prevMemAvg) * 100 : currMemAvg - prevMemAvg
|
||||||
@@ -142,7 +149,7 @@ async function detectAnomalies(targetId: number, serverName: string) {
|
|||||||
const DANGER_Z = 3.0
|
const DANGER_Z = 3.0
|
||||||
|
|
||||||
const hourSnapshots = await query<any>(`
|
const hourSnapshots = await query<any>(`
|
||||||
SELECT cpu_percent, memory_percent
|
SELECT cpu_percent, memory_total, memory_free
|
||||||
FROM server_snapshots
|
FROM server_snapshots
|
||||||
WHERE target_id = $1 AND is_online = 1
|
WHERE target_id = $1 AND is_online = 1
|
||||||
AND collected_at::timestamp >= NOW() - INTERVAL '1 hour'
|
AND collected_at::timestamp >= NOW() - INTERVAL '1 hour'
|
||||||
@@ -152,10 +159,16 @@ async function detectAnomalies(targetId: number, serverName: string) {
|
|||||||
if (hourSnapshots.length >= 10) {
|
if (hourSnapshots.length >= 10) {
|
||||||
const current = hourSnapshots[0]
|
const current = hourSnapshots[0]
|
||||||
const currCpu = current.cpu_percent ?? 0
|
const currCpu = current.cpu_percent ?? 0
|
||||||
const currMem = current.memory_percent ?? 0
|
// 메모리: (total - free) / total * 100
|
||||||
|
const calcMemPctZ = (s: any) => {
|
||||||
|
const total = Number(s.memory_total) || 0
|
||||||
|
const free = Number(s.memory_free) || 0
|
||||||
|
return total > 0 ? ((total - free) / total) * 100 : 0
|
||||||
|
}
|
||||||
|
const currMem = calcMemPctZ(current)
|
||||||
|
|
||||||
const cpuValues = hourSnapshots.map(s => s.cpu_percent ?? 0)
|
const cpuValues = hourSnapshots.map(s => s.cpu_percent ?? 0)
|
||||||
const memValues = hourSnapshots.map(s => s.memory_percent ?? 0)
|
const memValues = hourSnapshots.map(s => calcMemPctZ(s))
|
||||||
|
|
||||||
const cpuAvg = cpuValues.reduce((a, b) => a + b, 0) / cpuValues.length
|
const cpuAvg = cpuValues.reduce((a, b) => a + b, 0) / cpuValues.length
|
||||||
const memAvg = memValues.reduce((a, b) => a + b, 0) / memValues.length
|
const memAvg = memValues.reduce((a, b) => a + b, 0) / memValues.length
|
||||||
@@ -223,7 +236,7 @@ async function detectAnomalies(targetId: number, serverName: string) {
|
|||||||
const dayType = isWeekend ? 'weekend' : 'weekday'
|
const dayType = isWeekend ? 'weekend' : 'weekday'
|
||||||
|
|
||||||
const baselineData = await query<any>(`
|
const baselineData = await query<any>(`
|
||||||
SELECT cpu_percent, memory_percent
|
SELECT cpu_percent, memory_total, memory_free
|
||||||
FROM server_snapshots
|
FROM server_snapshots
|
||||||
WHERE target_id = $1 AND is_online = 1
|
WHERE target_id = $1 AND is_online = 1
|
||||||
AND collected_at::timestamp >= NOW() - INTERVAL '14 days'
|
AND collected_at::timestamp >= NOW() - INTERVAL '14 days'
|
||||||
@@ -236,18 +249,25 @@ async function detectAnomalies(targetId: number, serverName: string) {
|
|||||||
`, [targetId, currentHour, dayType])
|
`, [targetId, currentHour, dayType])
|
||||||
|
|
||||||
const currentSnapshot = await queryOne<any>(`
|
const currentSnapshot = await queryOne<any>(`
|
||||||
SELECT cpu_percent, memory_percent
|
SELECT cpu_percent, memory_total, memory_free
|
||||||
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 LIMIT 1
|
ORDER BY collected_at DESC LIMIT 1
|
||||||
`, [targetId])
|
`, [targetId])
|
||||||
|
|
||||||
|
// 메모리 계산 함수: (total - free) / total * 100
|
||||||
|
const calcMemPctBaseline = (s: any) => {
|
||||||
|
const total = Number(s.memory_total) || 0
|
||||||
|
const free = Number(s.memory_free) || 0
|
||||||
|
return total > 0 ? ((total - free) / total) * 100 : 0
|
||||||
|
}
|
||||||
|
|
||||||
if (baselineData.length >= 5 && currentSnapshot) {
|
if (baselineData.length >= 5 && currentSnapshot) {
|
||||||
const currCpu = currentSnapshot.cpu_percent ?? 0
|
const currCpu = currentSnapshot.cpu_percent ?? 0
|
||||||
const currMem = currentSnapshot.memory_percent ?? 0
|
const currMem = calcMemPctBaseline(currentSnapshot)
|
||||||
|
|
||||||
const cpuValues = baselineData.map(s => s.cpu_percent ?? 0)
|
const cpuValues = baselineData.map(s => s.cpu_percent ?? 0)
|
||||||
const memValues = baselineData.map(s => s.memory_percent ?? 0)
|
const memValues = baselineData.map(s => calcMemPctBaseline(s))
|
||||||
|
|
||||||
const cpuAvg = cpuValues.reduce((a, b) => a + b, 0) / cpuValues.length
|
const cpuAvg = cpuValues.reduce((a, b) => a + b, 0) / cpuValues.length
|
||||||
const memAvg = memValues.reduce((a, b) => a + b, 0) / memValues.length
|
const memAvg = memValues.reduce((a, b) => a + b, 0) / memValues.length
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ function formatTimeAgo(datetime: string | null): string {
|
|||||||
|
|
||||||
.container-area { display: flex; flex-wrap: wrap; gap: 10px; padding: 12px; align-content: flex-start; min-width: 140px; }
|
.container-area { display: flex; flex-wrap: wrap; gap: 10px; padding: 12px; align-content: flex-start; min-width: 140px; }
|
||||||
|
|
||||||
.container-card { width: 200px; padding: 10px; border-radius: 8px; border: 1px solid var(--border-color); background: var(--bg-secondary); cursor: pointer; transition: all 0.15s; overflow: hidden; }
|
.container-card { width: 208px; padding: 10px; border-radius: 8px; border: 1px solid var(--border-color); background: var(--bg-secondary); cursor: pointer; transition: all 0.15s; overflow: hidden; }
|
||||||
.container-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
.container-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||||
.container-card.normal { background: var(--container-normal-bg, #f0fdf4); border-color: var(--container-normal-border, #86efac); }
|
.container-card.normal { background: var(--container-normal-bg, #f0fdf4); border-color: var(--container-normal-border, #86efac); }
|
||||||
.container-card.warning { background: var(--container-warning-bg, #fefce8); border-color: var(--container-warning-border, #fde047); }
|
.container-card.warning { background: var(--container-warning-bg, #fefce8); border-color: var(--container-warning-border, #fde047); }
|
||||||
@@ -470,7 +470,7 @@ function formatTimeAgo(datetime: string | null): string {
|
|||||||
.card-uptime { font-size: 12px; color: var(--text-muted); flex-shrink: 0; margin-left: 8px; white-space: nowrap; }
|
.card-uptime { font-size: 12px; color: var(--text-muted); flex-shrink: 0; margin-left: 8px; white-space: nowrap; }
|
||||||
|
|
||||||
.card-metrics { display: flex; flex-wrap: wrap; gap: 5px 8px; }
|
.card-metrics { display: flex; flex-wrap: wrap; gap: 5px 8px; }
|
||||||
.card-metric { display: flex; align-items: center; gap: 4px; width: calc(50% - 4px); overflow: hidden; }
|
.card-metric { display: flex; align-items: center; gap: 4px; width: calc(50% - 4px); overflow: visible; }
|
||||||
.card-metric .label { font-size: 11px; color: var(--text-muted); width: 22px; flex-shrink: 0; font-weight: 500; }
|
.card-metric .label { font-size: 11px; color: var(--text-muted); width: 22px; flex-shrink: 0; font-weight: 500; }
|
||||||
.mini-bar { flex: 1; height: 6px; background: rgba(0,0,0,0.1); border-radius: 3px; overflow: hidden; min-width: 20px; }
|
.mini-bar { flex: 1; height: 6px; background: rgba(0,0,0,0.1); border-radius: 3px; overflow: hidden; min-width: 20px; }
|
||||||
.mini-fill { height: 100%; border-radius: 3px; }
|
.mini-fill { height: 100%; border-radius: 3px; }
|
||||||
@@ -478,7 +478,7 @@ function formatTimeAgo(datetime: string | null): string {
|
|||||||
.mini-fill.warning { background: #eab308; }
|
.mini-fill.warning { background: #eab308; }
|
||||||
.mini-fill.critical { background: #f97316; }
|
.mini-fill.critical { background: #f97316; }
|
||||||
.mini-fill.danger { background: #ef4444; }
|
.mini-fill.danger { background: #ef4444; }
|
||||||
.card-metric .value { font-size: 13px; font-weight: 700; color: var(--text-secondary); min-width: 38px; text-align: right; flex-shrink: 0; transition: all 0.3s; padding: 1px 2px; border-radius: 3px; }
|
.card-metric .value { font-size: 13px; font-weight: 700; color: var(--text-secondary); min-width: 44px; text-align: right; flex-shrink: 0; transition: all 0.3s; padding: 1px 2px; border-radius: 3px; }
|
||||||
.card-metric .value.mem-highlight { color: #dc2626; font-weight: 800; }
|
.card-metric .value.mem-highlight { color: #dc2626; font-weight: 800; }
|
||||||
.card-metric .value.net { color: var(--text-muted); font-weight: 600; }
|
.card-metric .value.net { color: var(--text-muted); font-weight: 600; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user