Files
Dashboard/server/routes/kuma.ts
T
2026-05-10 21:23:42 +02:00

100 lines
3.2 KiB
TypeScript

import { Router } from 'express'
import { io, Socket } from 'socket.io-client'
const router = Router()
interface MonitorInfo { id: number; name: string; type: string; active: boolean }
interface HeartbeatInfo { status: number; ping: number | null; beats: number[] }
let socket: Socket | null = null
let monitors: Record<number, MonitorInfo> = {}
let heartbeats: Record<number, HeartbeatInfo> = {}
let ready = false
const BEAT_HISTORY = 30
function startSocket() {
const host = process.env.KUMA_HOST
const user = process.env.KUMA_USER
const pass = process.env.KUMA_PASSWORD
if (!host || !user || !pass) return
socket = io(host, { transports: ['websocket'], reconnection: true })
socket.on('connect', () => {
socket!.emit('login', { username: user, password: pass, token: '' }, (res: { ok: boolean }) => {
if (!res.ok) console.error('[kuma] login failed')
})
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
socket.on('monitorList', (data: Record<string, any>) => {
monitors = {}
for (const m of Object.values(data)) {
monitors[m.id] = { id: m.id, name: m.name, type: m.type, active: m.active === true || m.active === 1 }
}
ready = true
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
socket.on('heartbeatList', (monitorId: number, beats: any[]) => {
if (!Array.isArray(beats) || beats.length === 0) return
const last = beats.at(-1)
const slice = beats.slice(-BEAT_HISTORY).map(b => b.status)
heartbeats[monitorId] = { status: last.status, ping: last.ping ?? null, beats: slice }
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
socket.on('heartbeat', (beat: any) => {
if (beat?.monitorID == null) return
const prev = heartbeats[beat.monitorID]
const beats = [...(prev?.beats ?? []), beat.status].slice(-BEAT_HISTORY)
heartbeats[beat.monitorID] = { status: beat.status, ping: beat.ping ?? null, beats }
})
socket.on('disconnect', () => { ready = false })
socket.on('connect_error', (err) => console.error('[kuma] connect error:', err.message))
}
function waitReady(timeoutMs = 6000): Promise<void> {
if (ready) return Promise.resolve()
return new Promise((resolve, reject) => {
const deadline = Date.now() + timeoutMs
const poll = setInterval(() => {
if (ready) { clearInterval(poll); resolve() }
else if (Date.now() > deadline) { clearInterval(poll); reject(new Error('Kuma not ready')) }
}, 100)
})
}
router.get('/monitors', async (_req, res) => {
try {
if (!socket) startSocket()
await waitReady()
const list = Object.values(monitors)
.filter(m => m.active)
.map(m => ({
id: m.id,
name: m.name,
type: m.type,
status: heartbeats[m.id]?.status ?? -1,
ping: heartbeats[m.id]?.ping ?? null,
beats: heartbeats[m.id]?.beats ?? [],
}))
.sort((a, b) => a.status - b.status)
res.json({
total: list.length,
up: list.filter(m => m.status === 1).length,
down: list.filter(m => m.status === 0).length,
monitors: list,
})
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Unknown error'
res.status(500).json({ error: msg })
}
})
export default router