Skip to main content
Glama

Smart-AI-Bridge

dashboard-server.jsโ€ข15.9 kB
// dashboard-server.js import express from 'express'; import http from 'http'; import { WebSocketServer } from 'ws'; import path from 'path'; import { fileURLToPath } from 'url'; import { BackendManager } from './backend-manager.js'; // Get __dirname equivalent in ES6 modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export class DashboardServer { constructor(options = {}) { this.app = express(); this.server = http.createServer(this.app); this.wss = new WebSocketServer({ server: this.server }); // Initialize backend manager this.backendManager = new BackendManager(options.backendConfigPath || './dashboard-config/backends.json'); // Store for external integrations this.usageAnalytics = options.usageAnalytics || null; this.conversationThreading = options.conversationThreading || null; this.adaptiveRouter = options.adaptiveRouter || null; this.clients = new Set(); this.isInitialized = false; this.setupMiddleware(); this.setupWebSocket(); this.setupRoutes(); this.setupErrorHandling(); } async initialize() { if (this.isInitialized) { return; } try { await this.backendManager.initialize(); this.setupBackendEventListeners(); this.isInitialized = true; console.log('Dashboard server initialized successfully'); } catch (error) { console.error('Failed to initialize dashboard server:', error); throw error; } } setupMiddleware() { // Parse JSON bodies this.app.use(express.json({ limit: '10mb' })); this.app.use(express.urlencoded({ extended: true })); // Serve static files from public/dashboard this.app.use('/dashboard', express.static(path.join(__dirname, 'public', 'dashboard'))); // Health check endpoint this.app.get('/health', (req, res) => { res.status(200).json({ status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime() }); }); // Redirect root to dashboard this.app.get('/', (req, res) => { res.redirect('/dashboard'); }); } setupWebSocket() { this.wss.on('connection', async (ws, req) => { console.log(`WebSocket client connected from ${req.socket.remoteAddress}`); this.clients.add(ws); try { // Send initial status to new client const backends = await this.backendManager.getAllBackends(); this.sendToClient(ws, 'initial_data', { backends, analytics: this.usageAnalytics ? await this.usageAnalytics.getRecentStats() : null, timestamp: new Date().toISOString() }); } catch (error) { console.error('Error sending initial data to client:', error); } ws.on('close', () => { this.clients.delete(ws); console.log('WebSocket client disconnected'); }); ws.on('error', (error) => { console.error('WebSocket error:', error); this.clients.delete(ws); }); }); } setupRoutes() { // Backend Management API Routes this.setupBackendRoutes(); // Analytics Routes (if available) if (this.usageAnalytics) { this.setupAnalyticsRoutes(); } // Conversation Threading Routes (if available) if (this.conversationThreading) { this.setupConversationRoutes(); } // Catch-all route to serve dashboard index.html for SPA this.app.get(/^\/dashboard(?:\/(.*))?$/, (req, res) => { res.sendFile(path.join(__dirname, 'public', 'dashboard', 'index.html')); }); } setupBackendRoutes() { // List all backends this.app.get('/api/backends/list', async (req, res) => { try { const backends = await this.backendManager.getAllBackends(); res.json({ success: true, backends }); } catch (error) { console.error('Error listing backends:', error); this.handleError(res, error, 'Failed to list backends'); } }); // Get specific backend status this.app.get('/api/backends/:backendId/status', async (req, res) => { try { const { backendId } = req.params; const status = await this.backendManager.getBackendStatus(backendId); res.json({ success: true, status }); } catch (error) { console.error(`Error getting backend ${req.params.id} status:`, error); this.handleError(res, error, 'Failed to get backend status'); } }); // Add new backend this.app.post('/api/backends/add', async (req, res) => { try { const backendConfig = req.body; const backendId = await this.backendManager.addBackend(backendConfig); // Broadcast to all clients this.broadcast('backend:added', { id: backendId, config: backendConfig }); res.status(201).json({ success: true, id: backendId }); } catch (error) { console.error('Error adding backend:', error); this.handleError(res, error, 'Failed to add backend'); } }); // Update backend configuration this.app.put('/api/backends/:backendId/update', async (req, res) => { try { const { backendId: id } = req.params; const updateData = req.body; const result = await this.backendManager.updateBackend(id, updateData); // Broadcast update this.broadcast('backend:updated', { id, updates: result }); res.json({ success: true, backend: result }); } catch (error) { console.error(`Error updating backend ${req.params.id}:`, error); this.handleError(res, error, 'Failed to update backend'); } }); // Delete backend this.app.delete('/api/backends/:backendId', async (req, res) => { try { const { backendId: id } = req.params; const result = await this.backendManager.removeBackend(id); // Broadcast deletion this.broadcast('backend:removed', { id }); res.json({ success: true, message: 'Backend removed successfully' }); } catch (error) { console.error(`Error removing backend ${req.params.id}:`, error); this.handleError(res, error, 'Failed to remove backend'); } }); // Start backend server this.app.post('/api/backends/:backendId/start', async (req, res) => { try { const { backendId: id } = req.params; // Broadcast starting event this.broadcast('backend:server_starting', { id, timestamp: new Date().toISOString() }); await this.backendManager.startBackend(id); // Broadcast ready event this.broadcast('backend:server_ready', { id, timestamp: new Date().toISOString() }); res.json({ success: true, message: `Backend ${id} started successfully` }); } catch (error) { console.error(`Error starting backend ${req.params.id}:`, error); this.broadcast('backend:server_error', { id: req.params.id, error: error.message }); this.handleError(res, error, 'Failed to start backend'); } }); // Stop backend server this.app.post('/api/backends/:backendId/stop', async (req, res) => { try { const { backendId: id } = req.params; // Broadcast stopping event this.broadcast('backend:server_stopping', { id, timestamp: new Date().toISOString() }); await this.backendManager.stopBackend(id); // Broadcast stopped event this.broadcast('backend:server_stopped', { id, timestamp: new Date().toISOString() }); res.json({ success: true, message: `Backend ${id} stopped successfully` }); } catch (error) { console.error(`Error stopping backend ${req.params.id}:`, error); this.handleError(res, error, 'Failed to stop backend'); } }); // Test backend connection this.app.post('/api/backends/:backendId/test', async (req, res) => { try { const { backendId: id } = req.params; const isHealthy = await this.backendManager.testBackendConnection(id); res.json({ success: true, healthy: isHealthy, message: isHealthy ? 'Backend is responding' : 'Backend is not responding' }); } catch (error) { console.error(`Error testing backend ${req.params.id}:`, error); this.handleError(res, error, 'Failed to test backend connection'); } }); // Detect Docker containers this.app.get('/api/backends/docker/detect', async (req, res) => { try { const containers = await this.backendManager.discoverVLLMContainers(); console.log(`Discovered ${containers.length} VLLM containers via API`); res.json({ success: true, containers }); } catch (error) { console.error('Error detecting Docker containers:', error); this.handleError(res, error, 'Failed to detect Docker containers'); } }); // Update backend priorities this.app.put('/api/backends/priorities', async (req, res) => { try { const { priorities } = req.body; // Update priorities for each backend for (const { id, priority } of priorities) { await this.backendManager.updateBackend(id, { priority }); } // Broadcast priority changes this.broadcast('backend:priorities_updated', priorities); res.json({ success: true, message: 'Priorities updated successfully' }); } catch (error) { console.error('Error updating backend priorities:', error); this.handleError(res, error, 'Failed to update backend priorities'); } }); } setupAnalyticsRoutes() { this.app.get('/api/analytics/stats', async (req, res) => { try { const stats = await this.usageAnalytics.getRecentStats(); res.json({ success: true, stats }); } catch (error) { this.handleError(res, error, 'Failed to get analytics stats'); } }); } setupConversationRoutes() { this.app.get('/api/conversations/all', async (req, res) => { try { const conversations = await this.conversationThreading.getConversations(); res.json({ success: true, conversations }); } catch (error) { this.handleError(res, error, 'Failed to get conversations'); } }); this.app.get('/api/conversations/:conversationId', async (req, res) => { try { const { conversationId: id } = req.params; const conversation = await this.conversationThreading.getConversation(id); res.json({ success: true, conversation }); } catch (error) { this.handleError(res, error, 'Failed to get conversation'); } }); } setupErrorHandling() { // 404 handler this.app.use((req, res) => { res.status(404).json({ error: 'Route not found' }); }); // Global error handler this.app.use((error, req, res, next) => { console.error('Unhandled error:', error); res.status(500).json({ error: 'Internal server error', message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong' }); }); } // WebSocket utility methods sendToClient(client, event, data) { if (client.readyState === 1) { // WebSocket.OPEN try { client.send(JSON.stringify({ event, data, timestamp: new Date().toISOString() })); } catch (error) { console.error('Error sending to client:', error); } } } broadcast(event, data) { const message = JSON.stringify({ event, data, timestamp: new Date().toISOString() }); this.clients.forEach(client => { if (client.readyState === 1) { // WebSocket.OPEN try { client.send(message); } catch (error) { console.error('Error broadcasting to client:', error); } } }); } handleError(res, error, defaultMessage) { const statusCode = error.statusCode || 500; res.status(statusCode).json({ success: false, error: defaultMessage, message: error.message, details: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } setupBackendEventListeners() { // Listen for backend events and broadcast to clients this.backendManager.on('backendStarted', (id) => { this.broadcast('backend:started', { id, timestamp: new Date().toISOString() }); }); this.backendManager.on('backendStopped', (id) => { this.broadcast('backend:stopped', { id, timestamp: new Date().toISOString() }); }); this.backendManager.on('backendAdded', (id) => { this.broadcast('backend:added', { id, timestamp: new Date().toISOString() }); }); this.backendManager.on('backendRemoved', (id) => { this.broadcast('backend:removed', { id, timestamp: new Date().toISOString() }); }); this.backendManager.on('backendUpdated', (id) => { this.broadcast('backend:updated', { id, timestamp: new Date().toISOString() }); }); this.backendManager.on('healthCheck', (data) => { this.broadcast('backend:health_check', data); }); this.backendManager.on('backendStatusChanged', (data) => { this.broadcast('backend:status_change', data); }); this.backendManager.on('error', (error) => { console.error('Backend manager error:', error); this.broadcast('backend:error', { error: error.message, timestamp: new Date().toISOString() }); }); } // Start the server async start(port = 3456) { if (!this.isInitialized) { await this.initialize(); } return new Promise((resolve, reject) => { // Add error handler for the server before listening const errorHandler = (error) => { console.error('Failed to start dashboard server:', error); this.server.removeListener('error', errorHandler); reject(error); }; this.server.once('error', errorHandler); this.server.listen(port, (error) => { this.server.removeListener('error', errorHandler); if (error) { console.error('Failed to start dashboard server:', error); reject(error); } else { console.log(`โœ… Dashboard server running on http://localhost:${port}`); console.log(`โœ… WebSocket server ready`); console.log(`โœ… Dashboard UI available at http://localhost:${port}/dashboard`); resolve(); } }); }); } // Graceful shutdown async stop() { console.log('Shutting down dashboard server...'); // Stop backend health monitoring this.backendManager.stopHealthMonitoring(); // Close WebSocket connections this.clients.forEach(client => { client.close(1001, 'Server shutting down'); }); // Close HTTP server return new Promise((resolve) => { this.server.close(() => { console.log('Dashboard server closed'); resolve(); }); }); } } // If run directly, start the server if (import.meta.url === `file://${process.argv[1]}`) { const PORT = process.env.DASHBOARD_PORT || 3456; const server = new DashboardServer(); server.start(PORT).catch((error) => { console.error('Failed to start dashboard server:', error); process.exit(1); }); // Handle graceful shutdown let isShuttingDown = false; const shutdown = async (signal) => { if (isShuttingDown) { console.log(`${signal} received again - forcing immediate exit`); process.exit(1); } isShuttingDown = true; console.log(`${signal} received, shutting down gracefully`); try { await server.stop(); process.exit(0); } catch (error) { console.error('Error during shutdown:', error); process.exit(1); } }; process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Platano78/Smart-AI-Bridge'

If you have feedback or need assistance with the MCP directory API, please join our Discord server