Files
vps-manager/server.js
mvbingham 1aafdc7b19 feat: initialize VPS fleet manager with server management functionality
- Add package.json for project metadata and dependencies
- Create server.js for API endpoints and server logic
- Implement data persistence using JSON file for server information
- Add HTML frontend (vps_manager.html) for server management interface
- Include Leaflet for map visualization of server locations
- Create start.bat for easy server startup on Windows
2026-05-25 20:19:24 -04:00

92 lines
2.7 KiB
JavaScript

const express = require('express');
const fs = require('fs');
const https = require('https');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 8095;
const DATA_FILE = path.join(__dirname, 'data', 'servers.json');
app.use(express.json());
app.use(express.static(__dirname));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'vps_manager.html'));
});
if (!fs.existsSync(path.join(__dirname, 'data'))) {
fs.mkdirSync(path.join(__dirname, 'data'));
}
function readData() {
try { return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); }
catch { return []; }
}
function writeData(data) {
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
}
function geocode(location) {
return new Promise((resolve) => {
const q = encodeURIComponent(location);
const options = {
hostname: 'nominatim.openstreetmap.org',
path: `/search?q=${q}&format=json&limit=1`,
headers: { 'User-Agent': 'vps-fleet-manager/1.0' }
};
https.get(options, (res) => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
try {
const r = JSON.parse(data);
resolve(r[0] ? { lat: parseFloat(r[0].lat), lng: parseFloat(r[0].lon) } : null);
} catch { resolve(null); }
});
}).on('error', () => resolve(null));
});
}
app.get('/api/servers', (req, res) => {
res.json(readData());
});
app.post('/api/servers', async (req, res) => {
const servers = readData();
const server = { ...req.body };
if (!server.id) server.id = 'srv_' + Date.now();
const existing = servers.find(s => s.id === server.id);
if (server.location && (!existing || existing.location !== server.location || !existing._lat)) {
const coords = await geocode(server.location);
if (coords) { server._lat = coords.lat; server._lng = coords.lng; }
} else if (existing && existing._lat) {
server._lat = existing._lat;
server._lng = existing._lng;
}
const idx = servers.findIndex(s => s.id === server.id);
if (idx > -1) servers[idx] = server;
else servers.push(server);
writeData(servers);
res.json(server);
});
app.delete('/api/servers/:id', (req, res) => {
writeData(readData().filter(s => s.id !== req.params.id));
res.json({ ok: true });
});
app.listen(PORT, '0.0.0.0', () => {
const { networkInterfaces } = require('os');
const nets = networkInterfaces();
const localIPs = Object.values(nets).flat()
.filter(n => n.family === 'IPv4' && !n.internal)
.map(n => n.address);
console.log(`\nvps/fleet running`);
console.log(` local → http://localhost:${PORT}`);
localIPs.forEach(ip => console.log(` network → http://${ip}:${PORT}`));
console.log('');
});