forked from VinokurovVE/tests
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
11 KiB
290 lines
11 KiB
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<ReadableStreamDefaultController> = 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}`);
|