|
| 1 | +import type { Request, Response } from 'express'; |
| 2 | +import si from 'systeminformation'; |
| 3 | + |
| 4 | +const writeSseEvent = (res: Response, event: string, data: unknown) => { |
| 5 | + res.write(`event: ${event}\n`); |
| 6 | + res.write(`data: ${JSON.stringify(data)}\n\n`); |
| 7 | +}; |
| 8 | + |
| 9 | +export const streamSystemStats = async (req: Request, res: Response) => { |
| 10 | + // Server-Sent Events (SSE) |
| 11 | + res.status(200); |
| 12 | + res.setHeader('Content-Type', 'text/event-stream; charset=utf-8'); |
| 13 | + res.setHeader('Cache-Control', 'no-cache, no-transform'); |
| 14 | + res.setHeader('Connection', 'keep-alive'); |
| 15 | + // Prevent nginx from buffering SSE |
| 16 | + res.setHeader('X-Accel-Buffering', 'no'); |
| 17 | + |
| 18 | + res.flushHeaders(); |
| 19 | + |
| 20 | + // Let the client know stream is alive |
| 21 | + writeSseEvent(res, 'ready', { ok: true, timestamp: new Date().toISOString() }); |
| 22 | + |
| 23 | + const intervalMs = 1000; |
| 24 | + |
| 25 | + const tick = async () => { |
| 26 | + try { |
| 27 | + const [load, mem] = await Promise.all([si.currentLoad(), si.mem()]); |
| 28 | + |
| 29 | + writeSseEvent(res, 'stats', { |
| 30 | + timestamp: new Date().toISOString(), |
| 31 | + cpu: { |
| 32 | + currentLoad: load.currentLoad, // % |
| 33 | + user: load.currentLoadUser, // % |
| 34 | + system: load.currentLoadSystem, // % |
| 35 | + idle: load.currentLoadIdle, // % |
| 36 | + }, |
| 37 | + memory: { |
| 38 | + total: mem.total, |
| 39 | + free: mem.free, |
| 40 | + used: mem.used, |
| 41 | + active: mem.active, |
| 42 | + available: mem.available, |
| 43 | + swaptotal: mem.swaptotal, |
| 44 | + swapused: mem.swapused, |
| 45 | + swapfree: mem.swapfree, |
| 46 | + }, |
| 47 | + }); |
| 48 | + } catch (error) { |
| 49 | + writeSseEvent(res, 'error', { |
| 50 | + message: error instanceof Error ? error.message : 'Unknown error', |
| 51 | + timestamp: new Date().toISOString(), |
| 52 | + }); |
| 53 | + } |
| 54 | + }; |
| 55 | + |
| 56 | + // send an immediate tick, then every second |
| 57 | + await tick(); |
| 58 | + const timer = setInterval(() => void tick(), intervalMs); |
| 59 | + |
| 60 | + req.on('close', () => { |
| 61 | + clearInterval(timer); |
| 62 | + }); |
| 63 | +}; |
| 64 | + |
| 65 | +export const getStorageInfo = async (_req: Request, res: Response) => { |
| 66 | + const sizes = await si.fsSize(); |
| 67 | + |
| 68 | + return res.json({ |
| 69 | + timestamp: new Date().toISOString(), |
| 70 | + filesystems: sizes.map((fs) => ({ |
| 71 | + fs: fs.fs, |
| 72 | + type: fs.type, |
| 73 | + mount: fs.mount, |
| 74 | + size: fs.size, |
| 75 | + used: fs.used, |
| 76 | + available: fs.available, |
| 77 | + use: fs.use, // % |
| 78 | + rw: fs.rw, |
| 79 | + })), |
| 80 | + }); |
| 81 | +}; |
| 82 | + |
0 commit comments