- Added api-security-hardening (helmet, rate limits) - Added nodejs-backend-patterns (error handling) - Added observability-monitoring (pino logging) - Added websocket-engineer (socket.io real-time updates) - Added docker (Multi-stage build, compose) - Added vitest (testing configuration and store tests) - Added data-visualizer (MetricsBar and HostChart) - Added infrastructure-monitoring/proxmox-admin/network-engineer types - Fixed UI accessibility and styling - Cleaned up node_modules tracking
108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import rateLimit from 'express-rate-limit';
|
|
import { createServer } from 'http';
|
|
import { Server, Socket } from 'socket.io';
|
|
import discoverRouter from './routes/discover';
|
|
import configRouter from './routes/config';
|
|
import statsRouter from './routes/stats';
|
|
import filesRouter from './routes/files';
|
|
import terminalRouter from './routes/terminal';
|
|
import { getHostConfigs } from './config';
|
|
import { requestLogger, logger } from './middleware/requestLogger';
|
|
import { errorHandler } from './middleware/errorHandler';
|
|
|
|
const app = express();
|
|
const httpServer = createServer(app);
|
|
const PORT = 3001;
|
|
|
|
// --- Socket.IO setup (websocket-engineer skill) ---
|
|
const io = new Server(httpServer, {
|
|
cors: {
|
|
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
|
|
credentials: true,
|
|
},
|
|
pingInterval: 25000,
|
|
pingTimeout: 10000,
|
|
});
|
|
|
|
io.on('connection', (socket: Socket) => {
|
|
logger.info({ socketId: socket.id }, 'Client connected via WebSocket');
|
|
|
|
socket.on('disconnect', (reason: string) => {
|
|
logger.info({ socketId: socket.id, reason }, 'Client disconnected');
|
|
});
|
|
});
|
|
|
|
// Export io so routes can emit events
|
|
export { io };
|
|
|
|
// --- Security middleware (api-security-hardening skill) ---
|
|
app.use(helmet({
|
|
contentSecurityPolicy: false, // Disable CSP for dev — configure per-environment in production
|
|
}));
|
|
|
|
// CORS — restrict to configured origins
|
|
app.use(cors({
|
|
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
|
|
credentials: true,
|
|
}));
|
|
|
|
// Rate limiting — general API
|
|
app.use('/api/', rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 200,
|
|
message: { status: 'error', message: 'Too many requests, please try again later' },
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
}));
|
|
|
|
// Stricter rate limiting for discovery (expensive operation)
|
|
app.use('/api/discover', rateLimit({
|
|
windowMs: 1 * 60 * 1000, // 1 minute
|
|
max: 10,
|
|
message: { status: 'error', message: 'Discovery rate limited — max 10 per minute' },
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
}));
|
|
|
|
// --- Body parsing ---
|
|
app.use(express.json({ limit: '1mb' }));
|
|
|
|
// --- Observability (observability-monitoring skill) ---
|
|
app.use(requestLogger);
|
|
|
|
// --- Health check ---
|
|
app.get('/api/health', (_req, res) => {
|
|
res.json({
|
|
status: 'ok',
|
|
uptime: process.uptime(),
|
|
timestamp: new Date().toISOString(),
|
|
connections: io.engine.clientsCount,
|
|
});
|
|
});
|
|
|
|
// --- Debug endpoint (dev only) ---
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
app.get('/api/debug-config', (_req, res) => {
|
|
const hosts = getHostConfigs();
|
|
res.json({ hosts });
|
|
});
|
|
}
|
|
|
|
// --- Routes ---
|
|
app.use('/api', discoverRouter);
|
|
app.use('/api', configRouter);
|
|
app.use('/api', statsRouter);
|
|
app.use('/api', filesRouter);
|
|
app.use('/api', terminalRouter);
|
|
|
|
// --- Global error handler (must be last) ---
|
|
app.use(errorHandler);
|
|
|
|
// --- Start server ---
|
|
httpServer.listen(PORT, () => {
|
|
logger.info(`Server running on http://localhost:${PORT}`);
|
|
});
|