- Add .env file for production deployment with reverse proxy - Add docker-compose.prod.yml for production profile - Add docker-compose.override.yml for local development - Update docker-compose.yml with all configurable variables - Update frontend to use VITE_* environment variables - Update backend to support CORS_ORIGINS and WEBHOOK_BASE_URL - Add vite.config.ts allowedHosts for reverse proxy - Add documentation for docker-compose and reverse proxy setup All URLs are now configurable via environment variables: - VITE_API_URL: Backend API endpoint - VITE_WEBHOOK_BASE_URL: Webhook base URL - VITE_INSTALL_SCRIPT_URL: Install script URL - VITE_APP_URL: Frontend URL - CORS_ORIGINS: Allowed CORS origins - WEBHOOK_BASE_URL: Backend webhook base URL
273 lines
9.4 KiB
JavaScript
273 lines
9.4 KiB
JavaScript
/**
|
|
* Fake Backend Server for LogWhispererAI
|
|
*
|
|
* Simulates AI analysis responses for frontend development
|
|
* without requiring real backend or OpenRouter API calls.
|
|
*
|
|
* @author LogWhispererAI Team
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
const DELAY_MS = parseInt(process.env.DELAY_MS) || 1500;
|
|
|
|
// CORS configuration - supports multiple origins via env var
|
|
// CORS_ORIGINS can be comma-separated list or '*' for all
|
|
const corsOrigins = process.env.CORS_ORIGINS || '*';
|
|
const corsOptions = corsOrigins === '*'
|
|
? { origin: '*', methods: ['GET', 'POST'], allowedHeaders: ['Content-Type'] }
|
|
: {
|
|
origin: corsOrigins.split(',').map(o => o.trim()),
|
|
methods: ['GET', 'POST'],
|
|
allowedHeaders: ['Content-Type'],
|
|
credentials: true
|
|
};
|
|
|
|
app.use(cors(corsOptions));
|
|
|
|
// Parse JSON bodies
|
|
app.use(express.json());
|
|
|
|
// Mock responses database
|
|
const MOCK_RESPONSES = {
|
|
// PostgreSQL errors
|
|
'memory': {
|
|
title: 'PostgreSQL Out of Memory',
|
|
description: 'Il database ha esaurito la memoria disponibile. Questo è spesso causato da query troppo pesanti o da un numero eccessivo di connessioni.',
|
|
command: 'ps aux | grep postgres | head -5 && free -h',
|
|
isSafe: true,
|
|
note: 'Verifica processi Postgres e memoria disponibile. Se necessario, riavvia il servizio con: sudo systemctl restart postgresql'
|
|
},
|
|
'oom': {
|
|
title: 'PostgreSQL Out of Memory',
|
|
description: 'Out Of Memory error rilevato. Il sistema ha esaurito la RAM disponibile.',
|
|
command: 'free -h && ps aux --sort=-%mem | head -10',
|
|
isSafe: true,
|
|
note: 'Identifica i processi che consumano più memoria e considera di aumentare la RAM o ottimizzare le query.'
|
|
},
|
|
// Nginx/Connection errors
|
|
'connection refused': {
|
|
title: 'Connessione al Backend Rifiutata',
|
|
description: 'Il reverse proxy (Nginx) non riesce a connettersi al backend. Il servizio potrebbe essere down.',
|
|
command: 'sudo systemctl status app-service && netstat -tlnp | grep 3000',
|
|
isSafe: true,
|
|
note: 'Verifica che il servizio backend sia in esecuzione. Se stopped, avvia con: sudo systemctl start app-service'
|
|
},
|
|
'502': {
|
|
title: 'Nginx 502 Bad Gateway',
|
|
description: 'Nginx riceve errore dal server upstream. Il backend non risponde correttamente.',
|
|
command: 'sudo systemctl status backend && tail -n 50 /var/log/backend/error.log',
|
|
isSafe: true,
|
|
note: 'Controlla i log del backend per errori specifici. Potrebbe essere necessario un riavvio.'
|
|
},
|
|
// Node.js errors
|
|
'econnrefused': {
|
|
title: 'Node.js - Connessione Database Rifiutata',
|
|
description: 'L\'applicazione Node non riesce a connettersi al database PostgreSQL sulla porta 5432.',
|
|
command: 'sudo systemctl status postgresql && sudo netstat -tlnp | grep 5432',
|
|
isSafe: true,
|
|
note: 'Verifica che PostgreSQL sia in esecuzione. Se down, avvia con: sudo systemctl start postgresql'
|
|
},
|
|
'exception': {
|
|
title: 'Node.js Exception',
|
|
description: 'Eccezione non gestita nell\'applicazione Node.js. Potrebbe essere un errore di connessione o configurazione.',
|
|
command: 'pm2 logs app --lines 50',
|
|
isSafe: true,
|
|
note: 'Controlla i log dell\'applicazione per l\'errore completo. Usa pm2 restart app se necessario.'
|
|
},
|
|
// Disk errors
|
|
'disk': {
|
|
title: 'Spazio su Disco Esaurito',
|
|
description: 'Il filesystem ha raggiunto il 100% di utilizzo. Nessuna scrittura possibile.',
|
|
command: 'df -h && du -sh /var/log/* | sort -hr | head -10',
|
|
isSafe: true,
|
|
note: 'Identifica quali directory occupano più spazio. Pulisci log vecchi con: sudo find /var/log -name "*.log" -mtime +7 -delete'
|
|
},
|
|
'no space': {
|
|
title: 'Spazio su Disco Esaurito',
|
|
description: 'Spazio insufficiente sul disco. Impossibile scrivere nuovi dati.',
|
|
command: 'df -h && du -sh /tmp /var/log /var/cache',
|
|
isSafe: true,
|
|
note: 'Libera spazio eliminando file temporanei o log vecchi.'
|
|
}
|
|
};
|
|
|
|
// Default response for unknown errors
|
|
const DEFAULT_RESPONSE = {
|
|
title: 'Errore di Sistema',
|
|
description: 'È stato rilevato un errore nel log. L\'analisi suggerisce di verificare lo stato dei servizi e le risorse di sistema.',
|
|
command: 'sudo systemctl status && df -h && free -h',
|
|
isSafe: true,
|
|
note: 'Esegui il comando sopra per verificare lo stato generale del sistema. Se il problema persiste, controlla i log specifici del servizio.'
|
|
};
|
|
|
|
/**
|
|
* Find matching mock response based on log content
|
|
* @param {string} logContent - The log content to analyze
|
|
* @returns {object} - Matching response or default
|
|
*/
|
|
function findMockResponse(logContent) {
|
|
const logLower = logContent.toLowerCase();
|
|
|
|
for (const [keyword, response] of Object.entries(MOCK_RESPONSES)) {
|
|
if (logLower.includes(keyword.toLowerCase())) {
|
|
return response;
|
|
}
|
|
}
|
|
|
|
return DEFAULT_RESPONSE;
|
|
}
|
|
|
|
/**
|
|
* POST /api/analyze
|
|
* Analyze log and return AI-like response
|
|
*/
|
|
app.post('/api/analyze', (req, res) => {
|
|
const { log } = req.body;
|
|
|
|
// Validate input
|
|
if (!log || typeof log !== 'string' || log.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Bad Request',
|
|
message: 'Il campo "log" è richiesto e non può essere vuoto',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
|
|
// Simulate AI processing delay
|
|
setTimeout(() => {
|
|
try {
|
|
const analysis = findMockResponse(log);
|
|
|
|
res.json({
|
|
success: true,
|
|
analysis: {
|
|
...analysis,
|
|
originalLog: log.substring(0, 500) // Include first 500 chars for reference
|
|
},
|
|
meta: {
|
|
processingTime: `${DELAY_MS}ms`,
|
|
model: 'fake-backend-mock-v1',
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error processing request:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Internal Server Error',
|
|
message: 'Errore durante l\'analisi del log',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}, DELAY_MS);
|
|
});
|
|
|
|
// Environment configuration for webhook URLs
|
|
const WEBHOOK_BASE_URL = process.env.WEBHOOK_BASE_URL || 'https://logwhisperer.ai/webhook';
|
|
|
|
/**
|
|
* POST /api/webhook
|
|
* Generate a fake webhook URL for onboarding
|
|
*/
|
|
app.post('/api/webhook', (req, res) => {
|
|
// Simulate generation delay (500ms - 1s)
|
|
const delay = Math.floor(Math.random() * 500) + 500;
|
|
|
|
setTimeout(() => {
|
|
try {
|
|
// Generate fake UUID v4
|
|
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
const r = Math.random() * 16 | 0;
|
|
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
return v.toString(16);
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
webhookUrl: `${WEBHOOK_BASE_URL}/${uuid}`,
|
|
uuid: uuid,
|
|
meta: {
|
|
generatedAt: new Date().toISOString(),
|
|
expiresIn: '30 days'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error generating webhook:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Internal Server Error',
|
|
message: 'Errore durante la generazione del webhook',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}, delay);
|
|
});
|
|
|
|
/**
|
|
* GET /health
|
|
* Health check endpoint
|
|
*/
|
|
app.get('/health', (req, res) => {
|
|
res.json({
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime(),
|
|
version: '1.0.0'
|
|
});
|
|
});
|
|
|
|
/**
|
|
* GET /
|
|
* Root endpoint with API info
|
|
*/
|
|
app.get('/', (req, res) => {
|
|
res.json({
|
|
name: 'LogWhispererAI - Fake Backend',
|
|
version: '1.0.0',
|
|
description: 'Mock API server for frontend development',
|
|
endpoints: {
|
|
'POST /api/analyze': 'Analyze log and return AI-like response',
|
|
'GET /health': 'Health check endpoint'
|
|
},
|
|
documentation: 'See docs/tools_fake_backend.md'
|
|
});
|
|
});
|
|
|
|
// Error handling middleware
|
|
app.use((err, req, res, next) => {
|
|
console.error('Unhandled error:', err);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Internal Server Error',
|
|
message: 'Errore imprevisto del server',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
// Start server - listen on all interfaces (0.0.0.0) to allow external connections
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`
|
|
╔══════════════════════════════════════════════════════════════╗
|
|
║ LogWhispererAI - Fake Backend Server ║
|
|
║ ║
|
|
║ 🚀 Server running on http://localhost:${PORT} ║
|
|
║ 📖 Documentation: docs/tools_fake_backend.md ║
|
|
║ ⏱️ Simulated delay: ${DELAY_MS}ms ║
|
|
║ ║
|
|
║ Endpoints: ║
|
|
║ POST /api/analyze - Analyze log and get mock AI response ║
|
|
║ POST /api/webhook - Generate fake webhook URL ║
|
|
║ GET /health - Health check ║
|
|
║ ║
|
|
║ Press Ctrl+C to stop ║
|
|
╚══════════════════════════════════════════════════════════════╝
|
|
`);
|
|
});
|
|
|
|
module.exports = app; // Export for testing
|