시스템 모니터
This commit is contained in:
78
backend/api/network/privnet/chart.get.ts
Normal file
78
backend/api/network/privnet/chart.get.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { getDb } from '../../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const { year, month, week } = query as { year?: string, month?: string, week?: string }
|
||||
|
||||
if (!year || !month || !week) {
|
||||
return { error: 'year, month, week are required' }
|
||||
}
|
||||
|
||||
// 해당 월의 첫날과 마지막날
|
||||
const y = parseInt(year)
|
||||
const m = parseInt(month)
|
||||
const w = parseInt(week)
|
||||
|
||||
// 해당 월의 첫날
|
||||
const firstDayOfMonth = new Date(y, m - 1, 1)
|
||||
const firstDayWeekday = firstDayOfMonth.getDay() // 0=일, 1=월, ...
|
||||
|
||||
// 주차의 시작일 계산 (월요일 기준)
|
||||
// 1주차: 1일이 포함된 주
|
||||
const mondayOffset = firstDayWeekday === 0 ? -6 : 1 - firstDayWeekday
|
||||
const firstMondayOfMonth = new Date(y, m - 1, 1 + mondayOffset)
|
||||
|
||||
// 선택한 주차의 월요일
|
||||
const weekStart = new Date(firstMondayOfMonth)
|
||||
weekStart.setDate(weekStart.getDate() + (w - 1) * 7)
|
||||
|
||||
// 주차의 일요일
|
||||
const weekEnd = new Date(weekStart)
|
||||
weekEnd.setDate(weekEnd.getDate() + 6)
|
||||
|
||||
// 해당 주의 날짜 목록 생성
|
||||
const weekDates: string[] = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const d = new Date(weekStart)
|
||||
d.setDate(d.getDate() + i)
|
||||
const dateStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
weekDates.push(dateStr)
|
||||
}
|
||||
|
||||
const db = getDb()
|
||||
|
||||
// 시작/종료 날짜
|
||||
const startDate = weekDates[0]
|
||||
const endDate = weekDates[6]
|
||||
|
||||
// 1시간 단위 성공률 조회
|
||||
const heatmapData = db.prepare(`
|
||||
SELECT
|
||||
date(checked_at) as date,
|
||||
strftime('%H', checked_at) || ':00' as time_slot,
|
||||
COUNT(*) as total_count,
|
||||
SUM(CASE WHEN is_success = 1 THEN 1 ELSE 0 END) as success_count,
|
||||
ROUND(SUM(CASE WHEN is_success = 1 THEN 1.0 ELSE 0.0 END) / COUNT(*) * 100, 1) as success_rate
|
||||
FROM privnet_logs
|
||||
WHERE date(checked_at) >= ? AND date(checked_at) <= ?
|
||||
GROUP BY date, time_slot
|
||||
ORDER BY date, time_slot
|
||||
`).all(startDate, endDate)
|
||||
|
||||
// 해당 월의 주차 수 계산
|
||||
const lastDayOfMonth = new Date(y, m, 0)
|
||||
const lastDate = lastDayOfMonth.getDate()
|
||||
|
||||
// 마지막 날이 몇 주차인지 계산
|
||||
const lastDayFromFirstMonday = Math.floor((lastDayOfMonth.getTime() - firstMondayOfMonth.getTime()) / (1000 * 60 * 60 * 24))
|
||||
const totalWeeks = Math.ceil((lastDayFromFirstMonday + 1) / 7)
|
||||
|
||||
return {
|
||||
heatmapData,
|
||||
weekDates,
|
||||
totalWeeks: Math.max(totalWeeks, 1),
|
||||
year: y,
|
||||
month: m,
|
||||
week: w
|
||||
}
|
||||
})
|
||||
33
backend/api/network/privnet/logs.get.ts
Normal file
33
backend/api/network/privnet/logs.get.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { getDb } from '../../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const { year, month, day, hour } = query as {
|
||||
year?: string, month?: string, day?: string, hour?: string
|
||||
}
|
||||
|
||||
if (!year || !month || !day || !hour) {
|
||||
return { error: 'year, month, day, hour are required' }
|
||||
}
|
||||
|
||||
const db = getDb()
|
||||
|
||||
// 해당 시간대 로그 조회
|
||||
const startTime = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${hour.padStart(2, '0')}:00:00`
|
||||
const endTime = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${hour.padStart(2, '0')}:59:59`
|
||||
|
||||
const logs = db.prepare(`
|
||||
SELECT
|
||||
l.id,
|
||||
l.checked_at,
|
||||
l.is_success,
|
||||
t.name as target_name,
|
||||
t.url as target_url
|
||||
FROM privnet_logs l
|
||||
JOIN privnet_targets t ON l.target_id = t.id
|
||||
WHERE l.checked_at >= ? AND l.checked_at <= ?
|
||||
ORDER BY l.checked_at DESC
|
||||
`).all(startTime, endTime)
|
||||
|
||||
return { logs }
|
||||
})
|
||||
11
backend/api/network/privnet/scheduler/start.post.ts
Normal file
11
backend/api/network/privnet/scheduler/start.post.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { privnetScheduler } from '../../../../utils/privnet-scheduler'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
privnetScheduler.start()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Privnet scheduler started',
|
||||
isRunning: privnetScheduler.getIsRunning()
|
||||
}
|
||||
})
|
||||
11
backend/api/network/privnet/scheduler/stop.post.ts
Normal file
11
backend/api/network/privnet/scheduler/stop.post.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { privnetScheduler } from '../../../../utils/privnet-scheduler'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
privnetScheduler.stop()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Privnet scheduler stopped',
|
||||
isRunning: privnetScheduler.getIsRunning()
|
||||
}
|
||||
})
|
||||
41
backend/api/network/privnet/status.get.ts
Normal file
41
backend/api/network/privnet/status.get.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { getDb } from '../../../utils/db'
|
||||
import { privnetScheduler } from '../../../utils/privnet-scheduler'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const db = getDb()
|
||||
|
||||
// 현재 상태 조회
|
||||
const status = db.prepare(`
|
||||
SELECT
|
||||
ps.*,
|
||||
pt.name as last_target_name,
|
||||
pt.url as last_target_url
|
||||
FROM privnet_status ps
|
||||
LEFT JOIN privnet_targets pt ON ps.last_target_id = pt.id
|
||||
WHERE ps.id = 1
|
||||
`).get()
|
||||
|
||||
// 최근 10개 로그
|
||||
const recentLogs = db.prepare(`
|
||||
SELECT
|
||||
pl.*,
|
||||
pt.name as target_name,
|
||||
pt.url as target_url
|
||||
FROM privnet_logs pl
|
||||
JOIN privnet_targets pt ON pl.target_id = pt.id
|
||||
ORDER BY pl.checked_at DESC
|
||||
LIMIT 10
|
||||
`).all()
|
||||
|
||||
// 활성 타겟 수
|
||||
const targetCount = db.prepare(`
|
||||
SELECT COUNT(*) as cnt FROM privnet_targets WHERE is_active = 1
|
||||
`).get() as { cnt: number }
|
||||
|
||||
return {
|
||||
status,
|
||||
recentLogs,
|
||||
targetCount: targetCount.cnt,
|
||||
schedulerRunning: privnetScheduler.getIsRunning()
|
||||
}
|
||||
})
|
||||
10
backend/api/network/privnet/targets/[id].delete.ts
Normal file
10
backend/api/network/privnet/targets/[id].delete.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { getDb } from '../../../../utils/db'
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const db = getDb()
|
||||
const id = getRouterParam(event, 'id')
|
||||
|
||||
db.prepare(`DELETE FROM privnet_targets WHERE id = ?`).run(id)
|
||||
|
||||
return { success: true }
|
||||
})
|
||||
29
backend/api/network/privnet/targets/[id].put.ts
Normal file
29
backend/api/network/privnet/targets/[id].put.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { getDb } from '../../../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = getDb()
|
||||
const id = getRouterParam(event, 'id')
|
||||
const body = await readBody(event)
|
||||
|
||||
const { name, url, is_active } = body
|
||||
|
||||
if (!name || !url) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'name과 url은 필수입니다'
|
||||
})
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
UPDATE privnet_targets
|
||||
SET name = ?, url = ?, is_active = ?, updated_at = datetime('now', 'localtime')
|
||||
WHERE id = ?
|
||||
`).run(name, url, is_active ? 1 : 0, id)
|
||||
|
||||
return {
|
||||
id: Number(id),
|
||||
name,
|
||||
url,
|
||||
is_active: is_active ? 1 : 0
|
||||
}
|
||||
})
|
||||
12
backend/api/network/privnet/targets/index.get.ts
Normal file
12
backend/api/network/privnet/targets/index.get.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getDb } from '../../../../utils/db'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const db = getDb()
|
||||
|
||||
const targets = db.prepare(`
|
||||
SELECT * FROM privnet_targets
|
||||
ORDER BY id ASC
|
||||
`).all()
|
||||
|
||||
return targets
|
||||
})
|
||||
27
backend/api/network/privnet/targets/index.post.ts
Normal file
27
backend/api/network/privnet/targets/index.post.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { getDb } from '../../../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = getDb()
|
||||
const body = await readBody(event)
|
||||
|
||||
const { name, url, is_active } = body
|
||||
|
||||
if (!name || !url) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'name과 url은 필수입니다'
|
||||
})
|
||||
}
|
||||
|
||||
const result = db.prepare(`
|
||||
INSERT INTO privnet_targets (name, url, is_active)
|
||||
VALUES (?, ?, ?)
|
||||
`).run(name, url, is_active ? 1 : 0)
|
||||
|
||||
return {
|
||||
id: result.lastInsertRowid,
|
||||
name,
|
||||
url,
|
||||
is_active: is_active ? 1 : 0
|
||||
}
|
||||
})
|
||||
78
backend/api/network/pubnet/chart.get.ts
Normal file
78
backend/api/network/pubnet/chart.get.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { getDb } from '../../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const { year, month, week } = query as { year?: string, month?: string, week?: string }
|
||||
|
||||
if (!year || !month || !week) {
|
||||
return { error: 'year, month, week are required' }
|
||||
}
|
||||
|
||||
// 해당 월의 첫날과 마지막날
|
||||
const y = parseInt(year)
|
||||
const m = parseInt(month)
|
||||
const w = parseInt(week)
|
||||
|
||||
// 해당 월의 첫날
|
||||
const firstDayOfMonth = new Date(y, m - 1, 1)
|
||||
const firstDayWeekday = firstDayOfMonth.getDay() // 0=일, 1=월, ...
|
||||
|
||||
// 주차의 시작일 계산 (월요일 기준)
|
||||
// 1주차: 1일이 포함된 주
|
||||
const mondayOffset = firstDayWeekday === 0 ? -6 : 1 - firstDayWeekday
|
||||
const firstMondayOfMonth = new Date(y, m - 1, 1 + mondayOffset)
|
||||
|
||||
// 선택한 주차의 월요일
|
||||
const weekStart = new Date(firstMondayOfMonth)
|
||||
weekStart.setDate(weekStart.getDate() + (w - 1) * 7)
|
||||
|
||||
// 주차의 일요일
|
||||
const weekEnd = new Date(weekStart)
|
||||
weekEnd.setDate(weekEnd.getDate() + 6)
|
||||
|
||||
// 해당 주의 날짜 목록 생성
|
||||
const weekDates: string[] = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const d = new Date(weekStart)
|
||||
d.setDate(d.getDate() + i)
|
||||
const dateStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
weekDates.push(dateStr)
|
||||
}
|
||||
|
||||
const db = getDb()
|
||||
|
||||
// 시작/종료 날짜
|
||||
const startDate = weekDates[0]
|
||||
const endDate = weekDates[6]
|
||||
|
||||
// 1시간 단위 성공률 조회
|
||||
const heatmapData = db.prepare(`
|
||||
SELECT
|
||||
date(checked_at) as date,
|
||||
strftime('%H', checked_at) || ':00' as time_slot,
|
||||
COUNT(*) as total_count,
|
||||
SUM(CASE WHEN is_success = 1 THEN 1 ELSE 0 END) as success_count,
|
||||
ROUND(SUM(CASE WHEN is_success = 1 THEN 1.0 ELSE 0.0 END) / COUNT(*) * 100, 1) as success_rate
|
||||
FROM pubnet_logs
|
||||
WHERE date(checked_at) >= ? AND date(checked_at) <= ?
|
||||
GROUP BY date, time_slot
|
||||
ORDER BY date, time_slot
|
||||
`).all(startDate, endDate)
|
||||
|
||||
// 해당 월의 주차 수 계산
|
||||
const lastDayOfMonth = new Date(y, m, 0)
|
||||
const lastDate = lastDayOfMonth.getDate()
|
||||
|
||||
// 마지막 날이 몇 주차인지 계산
|
||||
const lastDayFromFirstMonday = Math.floor((lastDayOfMonth.getTime() - firstMondayOfMonth.getTime()) / (1000 * 60 * 60 * 24))
|
||||
const totalWeeks = Math.ceil((lastDayFromFirstMonday + 1) / 7)
|
||||
|
||||
return {
|
||||
heatmapData,
|
||||
weekDates,
|
||||
totalWeeks: Math.max(totalWeeks, 1),
|
||||
year: y,
|
||||
month: m,
|
||||
week: w
|
||||
}
|
||||
})
|
||||
33
backend/api/network/pubnet/logs.get.ts
Normal file
33
backend/api/network/pubnet/logs.get.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { getDb } from '../../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const { year, month, day, hour } = query as {
|
||||
year?: string, month?: string, day?: string, hour?: string
|
||||
}
|
||||
|
||||
if (!year || !month || !day || !hour) {
|
||||
return { error: 'year, month, day, hour are required' }
|
||||
}
|
||||
|
||||
const db = getDb()
|
||||
|
||||
// 해당 시간대 로그 조회
|
||||
const startTime = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${hour.padStart(2, '0')}:00:00`
|
||||
const endTime = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${hour.padStart(2, '0')}:59:59`
|
||||
|
||||
const logs = db.prepare(`
|
||||
SELECT
|
||||
l.id,
|
||||
l.checked_at,
|
||||
l.is_success,
|
||||
t.name as target_name,
|
||||
t.url as target_url
|
||||
FROM pubnet_logs l
|
||||
JOIN pubnet_targets t ON l.target_id = t.id
|
||||
WHERE l.checked_at >= ? AND l.checked_at <= ?
|
||||
ORDER BY l.checked_at DESC
|
||||
`).all(startTime, endTime)
|
||||
|
||||
return { logs }
|
||||
})
|
||||
11
backend/api/network/pubnet/scheduler/start.post.ts
Normal file
11
backend/api/network/pubnet/scheduler/start.post.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { pubnetScheduler } from '../../../../utils/pubnet-scheduler'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
pubnetScheduler.start()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Pubnet scheduler started',
|
||||
isRunning: pubnetScheduler.getIsRunning()
|
||||
}
|
||||
})
|
||||
11
backend/api/network/pubnet/scheduler/stop.post.ts
Normal file
11
backend/api/network/pubnet/scheduler/stop.post.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { pubnetScheduler } from '../../../../utils/pubnet-scheduler'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
pubnetScheduler.stop()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Pubnet scheduler stopped',
|
||||
isRunning: pubnetScheduler.getIsRunning()
|
||||
}
|
||||
})
|
||||
41
backend/api/network/pubnet/status.get.ts
Normal file
41
backend/api/network/pubnet/status.get.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { getDb } from '../../../utils/db'
|
||||
import { pubnetScheduler } from '../../../utils/pubnet-scheduler'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const db = getDb()
|
||||
|
||||
// 현재 상태 조회
|
||||
const status = db.prepare(`
|
||||
SELECT
|
||||
ps.*,
|
||||
pt.name as last_target_name,
|
||||
pt.url as last_target_url
|
||||
FROM pubnet_status ps
|
||||
LEFT JOIN pubnet_targets pt ON ps.last_target_id = pt.id
|
||||
WHERE ps.id = 1
|
||||
`).get()
|
||||
|
||||
// 최근 10개 로그
|
||||
const recentLogs = db.prepare(`
|
||||
SELECT
|
||||
pl.*,
|
||||
pt.name as target_name,
|
||||
pt.url as target_url
|
||||
FROM pubnet_logs pl
|
||||
JOIN pubnet_targets pt ON pl.target_id = pt.id
|
||||
ORDER BY pl.checked_at DESC
|
||||
LIMIT 10
|
||||
`).all()
|
||||
|
||||
// 활성 타겟 수
|
||||
const targetCount = db.prepare(`
|
||||
SELECT COUNT(*) as cnt FROM pubnet_targets WHERE is_active = 1
|
||||
`).get() as { cnt: number }
|
||||
|
||||
return {
|
||||
status,
|
||||
recentLogs,
|
||||
targetCount: targetCount.cnt,
|
||||
schedulerRunning: pubnetScheduler.getIsRunning()
|
||||
}
|
||||
})
|
||||
10
backend/api/network/pubnet/targets/[id].delete.ts
Normal file
10
backend/api/network/pubnet/targets/[id].delete.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { getDb } from '../../../../utils/db'
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const db = getDb()
|
||||
const id = getRouterParam(event, 'id')
|
||||
|
||||
db.prepare(`DELETE FROM pubnet_targets WHERE id = ?`).run(id)
|
||||
|
||||
return { success: true }
|
||||
})
|
||||
29
backend/api/network/pubnet/targets/[id].put.ts
Normal file
29
backend/api/network/pubnet/targets/[id].put.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { getDb } from '../../../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = getDb()
|
||||
const id = getRouterParam(event, 'id')
|
||||
const body = await readBody(event)
|
||||
|
||||
const { name, url, is_active } = body
|
||||
|
||||
if (!name || !url) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'name과 url은 필수입니다'
|
||||
})
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
UPDATE pubnet_targets
|
||||
SET name = ?, url = ?, is_active = ?, updated_at = datetime('now', 'localtime')
|
||||
WHERE id = ?
|
||||
`).run(name, url, is_active ? 1 : 0, id)
|
||||
|
||||
return {
|
||||
id: Number(id),
|
||||
name,
|
||||
url,
|
||||
is_active: is_active ? 1 : 0
|
||||
}
|
||||
})
|
||||
12
backend/api/network/pubnet/targets/index.get.ts
Normal file
12
backend/api/network/pubnet/targets/index.get.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getDb } from '../../../../utils/db'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const db = getDb()
|
||||
|
||||
const targets = db.prepare(`
|
||||
SELECT * FROM pubnet_targets
|
||||
ORDER BY id ASC
|
||||
`).all()
|
||||
|
||||
return targets
|
||||
})
|
||||
27
backend/api/network/pubnet/targets/index.post.ts
Normal file
27
backend/api/network/pubnet/targets/index.post.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { getDb } from '../../../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = getDb()
|
||||
const body = await readBody(event)
|
||||
|
||||
const { name, url, is_active } = body
|
||||
|
||||
if (!name || !url) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'name과 url은 필수입니다'
|
||||
})
|
||||
}
|
||||
|
||||
const result = db.prepare(`
|
||||
INSERT INTO pubnet_targets (name, url, is_active)
|
||||
VALUES (?, ?, ?)
|
||||
`).run(name, url, is_active ? 1 : 0)
|
||||
|
||||
return {
|
||||
id: result.lastInsertRowid,
|
||||
name,
|
||||
url,
|
||||
is_active: is_active ? 1 : 0
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user