const express = require('express'); const path = require('path'); const fs = require('fs'); const SporeApiClient = require('./src/client'); const app = express(); const PORT = process.env.PORT || 3001; // Initialize the SPORE API client const sporeClient = new SporeApiClient('http://10.0.1.60'); // Serve static files from public directory app.use(express.static(path.join(__dirname, 'public'))); // Serve the main HTML page app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // API endpoint to get cluster members app.get('/api/cluster/members', async (req, res) => { try { const members = await sporeClient.getClusterStatus(); res.json(members); } catch (error) { console.error('Error fetching cluster members:', error); res.status(500).json({ error: 'Failed to fetch cluster members', message: error.message }); } }); // API endpoint to get task status app.get('/api/tasks/status', async (req, res) => { try { const taskStatus = await sporeClient.getTaskStatus(); res.json(taskStatus); } catch (error) { console.error('Error fetching task status:', error); res.status(500).json({ error: 'Failed to fetch task status', message: error.message }); } }); // API endpoint to get system status app.get('/api/node/status', async (req, res) => { try { const systemStatus = await sporeClient.getSystemStatus(); res.json(systemStatus); } catch (error) { console.error('Error fetching system status:', error); res.status(500).json({ error: 'Failed to fetch system status', message: error.message }); } }); // Proxy endpoint to get status from a specific node app.get('/api/node/status/:ip', async (req, res) => { try { const nodeIp = req.params.ip; // Create a temporary client for the specific node const nodeClient = new SporeApiClient(`http://${nodeIp}`); const nodeStatus = await nodeClient.getSystemStatus(); res.json(nodeStatus); } catch (error) { console.error(`Error fetching status from node ${req.params.ip}:`, error); res.status(500).json({ error: `Failed to fetch status from node ${req.params.ip}`, message: error.message }); } }); // File upload endpoint for firmware updates app.post('/api/node/update', express.raw({ type: 'multipart/form-data', limit: '50mb' }), async (req, res) => { try { const nodeIp = req.query.ip || req.headers['x-node-ip']; if (!nodeIp) { return res.status(400).json({ error: 'Node IP address is required', message: 'Please provide the target node IP address' }); } // Parse multipart form data manually const boundary = req.headers['content-type']?.split('boundary=')[1]; if (!boundary) { return res.status(400).json({ error: 'Invalid content type', message: 'Expected multipart/form-data with boundary' }); } // Parse the multipart data to extract the file const fileData = parseMultipartData(req.body, boundary); if (!fileData.file) { return res.status(400).json({ error: 'No file data received', message: 'Please select a firmware file to upload' }); } console.log(`Uploading firmware to node ${nodeIp}, file size: ${fileData.file.data.length} bytes, filename: ${fileData.file.filename}`); // Create a temporary client for the specific node const nodeClient = new SporeApiClient(`http://${nodeIp}`); // Send the firmware data to the node using multipart form data const updateResult = await nodeClient.updateFirmware(fileData.file.data, fileData.file.filename); res.json({ success: true, message: 'Firmware uploaded successfully', nodeIp: nodeIp, fileSize: fileData.file.data.length, filename: fileData.file.filename, result: updateResult }); } catch (error) { console.error('Error uploading firmware:', error); res.status(500).json({ error: 'Failed to upload firmware', message: error.message }); } }); // Helper function to parse multipart form data function parseMultipartData(buffer, boundary) { const data = {}; const boundaryBuffer = Buffer.from(`--${boundary}`); const endBoundaryBuffer = Buffer.from(`--${boundary}--`); let start = 0; let end = 0; while (true) { // Find the start of the next part start = buffer.indexOf(boundaryBuffer, end); if (start === -1) break; // Find the end of this part end = buffer.indexOf(boundaryBuffer, start + boundaryBuffer.length); if (end === -1) { end = buffer.indexOf(endBoundaryBuffer, start + boundaryBuffer.length); if (end === -1) break; } // Extract the part content const partBuffer = buffer.slice(start + boundaryBuffer.length + 2, end); // Parse headers and content const headerEnd = partBuffer.indexOf('\r\n\r\n'); if (headerEnd === -1) continue; const headers = partBuffer.slice(0, headerEnd).toString(); const content = partBuffer.slice(headerEnd + 4); // Parse the Content-Disposition header to get field name and filename const nameMatch = headers.match(/name="([^"]+)"/); const filenameMatch = headers.match(/filename="([^"]+)"/); if (nameMatch) { const fieldName = nameMatch[1]; const filename = filenameMatch ? filenameMatch[1] : null; data[fieldName] = { data: content, filename: filename }; } } return data; } // Start the server app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });