시스템 모니터

This commit is contained in:
2025-12-28 12:03:48 +09:00
parent dbae6649bc
commit a871ec8008
73 changed files with 21354 additions and 1 deletions

View File

@@ -0,0 +1,30 @@
import { getDb } from '../../../utils/db'
export default defineEventHandler((event) => {
const query = getQuery(event)
const targetId = query.target_id as string
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
const db = getDb()
// 최신 수집 시간 기준 컨테이너 목록
const containers = db.prepare(`
SELECT DISTINCT container_name
FROM server_containers
WHERE target_id = ?
AND collected_at = (
SELECT MAX(collected_at)
FROM server_containers
WHERE target_id = ?
)
ORDER BY container_name ASC
`).all(targetId, targetId)
return containers.map((c: any) => c.container_name)
})

View File

@@ -0,0 +1,57 @@
import { getDb } from '../../../utils/db'
export default defineEventHandler((event) => {
const query = getQuery(event)
const targetId = query.target_id as string
const period = (query.period as string) || '1h'
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
const periodMap: Record<string, string> = {
'1h': '-1 hour',
'2h': '-2 hours',
'3h': '-3 hours',
'4h': '-4 hours',
'5h': '-5 hours',
'6h': '-6 hours',
'12h': '-12 hours',
'18h': '-18 hours',
'24h': '-24 hours',
'7d': '-7 days',
'30d': '-30 days'
}
const timeOffset = periodMap[period] || '-1 hour'
const db = getDb()
const containers = db.prepare(`
SELECT
container_id,
container_name,
container_status,
cpu_percent,
memory_usage,
memory_limit,
memory_percent,
uptime,
network_rx,
network_tx,
collected_at
FROM server_containers
WHERE target_id = ?
AND collected_at >= datetime('now', 'localtime', ?)
ORDER BY collected_at ASC, container_name ASC
`).all(targetId, timeOffset)
return {
target_id: targetId,
period,
data: containers
}
})

View File

@@ -0,0 +1,29 @@
import { getDb } from '../../../utils/db'
export default defineEventHandler((event) => {
const query = getQuery(event)
const targetId = query.target_id as string
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
const db = getDb()
// 최신 수집 시간 기준 디스크 목록 (물리 디스크만)
const disks = db.prepare(`
SELECT DISTINCT device_name, mount_point, fs_type, disk_total, disk_used, disk_percent
FROM server_disks
WHERE target_id = ?
AND collected_at = (SELECT MAX(collected_at) FROM server_disks WHERE target_id = ?)
AND device_name NOT LIKE '%loop%'
AND mount_point NOT LIKE '%/snap%'
AND fs_type NOT IN ('tmpfs', 'squashfs', 'overlay')
ORDER BY mount_point ASC
`).all(targetId, targetId)
return disks
})

View File

@@ -0,0 +1,54 @@
import { getDb } from '../../../utils/db'
export default defineEventHandler((event) => {
const query = getQuery(event)
const targetId = query.target_id as string
const period = (query.period as string) || '1h'
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
const periodMap: Record<string, string> = {
'1h': '-1 hour',
'2h': '-2 hours',
'3h': '-3 hours',
'4h': '-4 hours',
'5h': '-5 hours',
'6h': '-6 hours',
'12h': '-12 hours',
'18h': '-18 hours',
'24h': '-24 hours',
'7d': '-7 days',
'30d': '-30 days'
}
const timeOffset = periodMap[period] || '-1 hour'
const db = getDb()
const disks = db.prepare(`
SELECT
disk_id,
device_name,
mount_point,
fs_type,
disk_total,
disk_used,
disk_percent,
collected_at
FROM server_disks
WHERE target_id = ?
AND collected_at >= datetime('now', 'localtime', ?)
ORDER BY collected_at ASC, mount_point ASC
`).all(targetId, timeOffset)
return {
target_id: targetId,
period,
data: disks
}
})

View File

@@ -0,0 +1,32 @@
import { getDb } from '../../../utils/db'
export default defineEventHandler((event) => {
const query = getQuery(event)
const targetId = query.target_id as string
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
const db = getDb()
// 최신 스냅샷
const snapshot = db.prepare(`
SELECT
s.*,
t.server_name,
t.server_ip,
t.glances_url,
t.collect_interval
FROM server_snapshots s
JOIN server_targets t ON s.target_id = t.target_id
WHERE s.target_id = ?
ORDER BY s.collected_at DESC
LIMIT 1
`).get(targetId)
return snapshot || null
})

View File

@@ -0,0 +1,54 @@
import { getDb } from '../../../utils/db'
export default defineEventHandler((event) => {
const query = getQuery(event)
const targetId = query.target_id as string
const period = (query.period as string) || '1h'
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
const periodMap: Record<string, string> = {
'1h': '-1 hour',
'2h': '-2 hours',
'3h': '-3 hours',
'4h': '-4 hours',
'5h': '-5 hours',
'6h': '-6 hours',
'12h': '-12 hours',
'18h': '-18 hours',
'24h': '-24 hours',
'7d': '-7 days',
'30d': '-30 days'
}
const timeOffset = periodMap[period] || '-1 hour'
const db = getDb()
const networks = db.prepare(`
SELECT
network_id,
interface_name,
bytes_recv,
bytes_sent,
speed_recv,
speed_sent,
is_up,
collected_at
FROM server_networks
WHERE target_id = ?
AND collected_at >= datetime('now', 'localtime', ?)
ORDER BY collected_at ASC, interface_name ASC
`).all(targetId, timeOffset)
return {
target_id: targetId,
period,
data: networks
}
})

View File

@@ -0,0 +1,59 @@
import { getDb } from '../../../utils/db'
export default defineEventHandler((event) => {
const query = getQuery(event)
const targetId = query.target_id as string
const period = (query.period as string) || '1h'
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
// 기간별 시간 계산
const periodMap: Record<string, string> = {
'1h': '-1 hour',
'2h': '-2 hours',
'3h': '-3 hours',
'4h': '-4 hours',
'5h': '-5 hours',
'6h': '-6 hours',
'12h': '-12 hours',
'18h': '-18 hours',
'24h': '-24 hours',
'7d': '-7 days',
'30d': '-30 days'
}
const timeOffset = periodMap[period] || '-1 hour'
const db = getDb()
const snapshots = db.prepare(`
SELECT
snapshot_id,
cpu_percent,
cpu_temp,
load_percent,
memory_percent,
memory_used,
memory_total,
swap_percent,
swap_used,
swap_total,
is_online,
collected_at
FROM server_snapshots
WHERE target_id = ?
AND collected_at >= datetime('now', 'localtime', ?)
ORDER BY collected_at ASC
`).all(targetId, timeOffset)
return {
target_id: targetId,
period,
data: snapshots
}
})

View File

@@ -0,0 +1,6 @@
import { startServerScheduler } from '../../../utils/server-scheduler'
export default defineEventHandler(() => {
startServerScheduler()
return { success: true, message: 'Server scheduler started' }
})

View File

@@ -0,0 +1,6 @@
import { stopServerScheduler } from '../../../utils/server-scheduler'
export default defineEventHandler(() => {
stopServerScheduler()
return { success: true, message: 'Server scheduler stopped' }
})

View File

@@ -0,0 +1,5 @@
import { getServerSchedulerStatus } from '../../utils/server-scheduler'
export default defineEventHandler(() => {
return getServerSchedulerStatus()
})

View File

@@ -0,0 +1,27 @@
import { getDb } from '../../../utils/db'
import { refreshServerTimer } from '../../../utils/server-scheduler'
export default defineEventHandler(async (event) => {
const targetId = getRouterParam(event, 'id')
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
// 스케줄러에서 제거
refreshServerTimer(Number(targetId))
const db = getDb()
const result = db.prepare(`
DELETE FROM server_targets WHERE target_id = ?
`).run(targetId)
return {
success: true,
changes: result.changes
}
})

View File

@@ -0,0 +1,36 @@
import { getDb } from '../../../utils/db'
import { refreshServerTimer } from '../../../utils/server-scheduler'
export default defineEventHandler(async (event) => {
const targetId = getRouterParam(event, 'id')
const body = await readBody(event)
const { server_name, server_ip, glances_url, is_active, collect_interval } = body
if (!targetId) {
throw createError({
statusCode: 400,
message: 'target_id is required'
})
}
const db = getDb()
const result = db.prepare(`
UPDATE server_targets
SET server_name = ?,
server_ip = ?,
glances_url = ?,
is_active = ?,
collect_interval = ?,
updated_at = datetime('now', 'localtime')
WHERE target_id = ?
`).run(server_name, server_ip, glances_url, is_active ? 1 : 0, collect_interval || 60, targetId)
// 스케줄러에 반영
refreshServerTimer(Number(targetId))
return {
success: true,
changes: result.changes
}
})

View File

@@ -0,0 +1,12 @@
import { getDb } from '../../../utils/db'
export default defineEventHandler(() => {
const db = getDb()
const targets = db.prepare(`
SELECT * FROM server_targets
ORDER BY target_id ASC
`).all()
return targets
})

View File

@@ -0,0 +1,33 @@
import { getDb } from '../../../utils/db'
import { refreshServerTimer } from '../../../utils/server-scheduler'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const { server_name, server_ip, glances_url, is_active = 1, collect_interval = 60 } = body
if (!server_name || !server_ip || !glances_url) {
throw createError({
statusCode: 400,
message: 'server_name, server_ip, glances_url are required'
})
}
const db = getDb()
const result = db.prepare(`
INSERT INTO server_targets (server_name, server_ip, glances_url, is_active, collect_interval)
VALUES (?, ?, ?, ?, ?)
`).run(server_name, server_ip, glances_url, is_active ? 1 : 0, collect_interval)
const targetId = result.lastInsertRowid as number
// 스케줄러에 반영
if (is_active) {
refreshServerTimer(targetId)
}
return {
success: true,
target_id: targetId
}
})