import { serve } from 'bun'; import { Database } from 'bun:sqlite'; import { EventEmitter } from 'events'; const db = new Database('./data/servers.db'); // Initialize the database table if it doesn't exist db.run(` CREATE TABLE IF NOT EXISTS servers ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, hostname TEXT NOT NULL, port INTEGER NOT NULL, ping_rate INTEGER NOT NULL, status TEXT DEFAULT 'unknown', monitoring INTEGER DEFAULT 1 ) `); // In-memory state to store server details const serverStates: { [id: number]: { id: number; name: string; hostname: string; port: number; ping_rate: number; status: 'up' | 'down' | 'unknown'; monitoring: number; } } = {}; // Event emitter to handle server updates const serverMonitor = new EventEmitter(); const clients: Set = new Set() class ServerState { id: number | undefined; name: string | undefined; hostname: string | undefined; port: number | undefined; ping_rate: number | undefined; status: 'up' | 'down' | 'unknown' | undefined; monitoring: number | undefined; } // Function to initialize server states from the database async function initializeServerStates() { const servers = db.query('SELECT * FROM servers').as(ServerState).all(); servers.forEach((server: ServerState) => { if (server.id && server.name && server.hostname && server.port && server.ping_rate && server.status) { serverStates[server.id] = { id: server.id, name: server.name, hostname: server.hostname, port: server.port, ping_rate: server.ping_rate, status: server.status, monitoring: server.monitoring || 0 }; } }); } // Start monitoring for a specific server by its ID async function monitorServer(serverId: number) { if (!serverStates[serverId]) return while (true) { if (serverStates[serverId]) { await checkServer(serverId); await new Promise(resolve => setTimeout(resolve, serverStates[serverId].ping_rate * 1000)); } else { console.log("Stopped monitoring ", serverId) return false } } } // Function to check the server availability async function checkServer(serverId: number) { const server = serverStates[serverId]; if (!server) return; if (server.monitoring === 0) return try { const checkSocket = await Bun.connect({ hostname: server.hostname, port: server.port, socket: { data(socket, data) { }, open(socket) { console.log(`[${new Date().toISOString()}] ${server.name} (${server.hostname}:${server.port}) is up.`); if (serverStates[serverId].status !== 'up') { db.run('UPDATE servers SET status = ? WHERE id = ?', ['up', serverId]); serverStates[serverId].status = 'up'; notifyClients() } socket.end(); }, connectError(socket, error) { console.error(`[${new Date().toISOString()}] ${server.name} (${server.hostname}:${server.port}) is down!`); if (serverStates[serverId].status !== 'down') { db.run('UPDATE servers SET status = ? WHERE id = ?', ['down', serverId]); serverStates[serverId].status = 'down'; notifyClients() } }, error(socket, error) { console.error(`[${new Date().toISOString()}] Error connecting to ${server.name} (${server.hostname}:${server.port}):`, error); if (serverStates[serverId].status !== 'down') { db.run('UPDATE servers SET status = ? WHERE id = ?', ['down', serverId]); serverStates[serverId].status = 'down'; notifyClients() } }, timeout(socket) { console.error(`[${new Date().toISOString()}] Connection to ${server.name} (${server.hostname}:${server.port}) timed out!`); if (serverStates[serverId].status !== 'down') { db.run('UPDATE servers SET status = ? WHERE id = ?', ['down', serverId]); serverStates[serverId].status = 'down'; notifyClients() } } } }) } catch (error) { console.error(`[${new Date().toISOString()}] Unexpected error checking ${server.name} (${server.hostname}:${server.port}):`, error); if (serverStates[serverId].status !== 'down') { db.run('UPDATE servers SET status = ? WHERE id = ?', ['down', serverId]); serverStates[serverId].status = 'down'; } } } // Notify all connected clients of server state changes function notifyClients() { const data = JSON.stringify(Object.values(serverStates)); clients.forEach(client => client.enqueue(`data: ${data}\n\n`)); } // Start monitoring all servers from the initial state async function startMonitoring() { Object.keys(serverStates).forEach(serverId => { monitorServer(parseInt(serverId, 10)); }) console.log('Monitoring started for existing servers.'); } // Initialize states and start monitoring initializeServerStates().then(startMonitoring); // Event listener for adding new servers serverMonitor.on('newServer', (serverId) => { if (!serverStates[serverId]) return; monitorServer(serverId); }); // API Server to manage servers const server = serve({ port: process.env.MONITOR_PORT || 1234, fetch: async (req) => { const url = new URL(req.url); const pathname = url.pathname; const method = req.method; if (pathname === '/watch' && method === 'GET') { const headers = { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*' }; const client = new Response( new ReadableStream({ start(controller) { clients.add(controller); controller.enqueue(`data: ${JSON.stringify(Object.values(serverStates))}\n\n`) }, cancel(controller) { clients.delete(controller); } }), { headers } ); return client; } if (pathname === '/servers' && method === 'GET') { return new Response(JSON.stringify(Object.values(serverStates)), { headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, status: 200 }); } if (pathname === '/server' && method === 'POST') { const data = await req.json(); const { name, hostname, port, ping_rate } = data; if (!name || !hostname || !port || !ping_rate) { return new Response('Missing fields', { status: 400 }); } db.run('INSERT INTO servers (name, hostname, port, ping_rate) VALUES (?, ?, ?, ?)', name, hostname, port, ping_rate); const newServer = db.query('SELECT * FROM servers WHERE id = last_insert_rowid()').as(ServerState).get(); if (newServer && newServer.id && newServer.name && newServer.hostname && newServer.port && newServer.ping_rate && newServer.status && newServer.monitoring) { serverStates[newServer.id] = { id: newServer.id, name: newServer.name, hostname: newServer.hostname, port: newServer.port, ping_rate: newServer.ping_rate, status: newServer.status || 'unknown', monitoring: newServer.monitoring }; } if (newServer && newServer.id) { serverMonitor.emit('newServer', newServer.id); return new Response('Server added', { status: 201, headers: { 'Access-Control-Allow-Origin': '*' } }); } } if (pathname.startsWith('/server/') && method === 'PUT') { const { searchParams } = new URL(req.url) const id = pathname.split('/').pop() const name = searchParams.get('name') const hostname = searchParams.get('hostname') const port = searchParams.get('port') const ping_rate = searchParams.get('ping_rate') const status = searchParams.get('status') const monitoring = searchParams.get('monitoring') if (!id) { return new Response('Invalid ID', { status: 400, headers: { 'Access-Control-Allow-Origin': '*' } }); } // Fetch the current state of the server const currentServer = serverStates[parseInt(id)]; if (!currentServer) { return new Response('Server not found', { status: 404, headers: { 'Access-Control-Allow-Origin': '*' } }); } // Prepare the updated server data const updatedServer: any = { name: name || currentServer.name, hostname: hostname || currentServer.hostname, port: port || currentServer.port, ping_rate: ping_rate || currentServer.ping_rate, status: status || currentServer.status, monitoring: monitoring || currentServer.monitoring }; // Update the database db.run( `UPDATE servers SET name = ?, hostname = ?, port = ?, ping_rate = ?, status = ?, monitoring = ? WHERE id = ?`, updatedServer.name, updatedServer.hostname, updatedServer.port, updatedServer.ping_rate, updatedServer.status, updatedServer.monitoring, id ); // Update the in-memory state serverStates[parseInt(id)] = { ...serverStates[parseInt(id)], // Retain existing fields ...updatedServer // Apply updates }; return new Response('Server updated', { status: 200, headers: { 'Access-Control-Allow-Origin': '*' } }); } if (pathname.startsWith('/server/') && method === 'DELETE') { const id = pathname.split('/').pop(); if (id) { db.run('DELETE FROM servers WHERE id = ?', [parseInt(id)]) delete serverStates[parseInt(id)] return new Response('Server deleted', { status: 200, headers: { 'Access-Control-Allow-Origin': '*' } }) } } return new Response('Not Found', { status: 404, headers: { 'Access-Control-Allow-Origin': '*' } }); } }); console.log(`API server and monitoring running on http://localhost:${server.port}`);