190 lines
5.6 KiB
JavaScript
190 lines
5.6 KiB
JavaScript
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}`);
|
|
});
|