Compare commits

...

7 Commits

Author SHA1 Message Date
lucasacchi
1dc2f658a0 Update README.md
chores
2026-04-07 11:30:19 +02:00
lucasacchi
a3a641595d Update README.md
added link to 'mockupAWS - Backend Profiler & Cost Estimator'
2026-04-07 11:29:35 +02:00
Luca Sacchi Ricciardi
c2c922c1b7 docs: add demo link to README
Add link to live demo at https://logwhispererai.lab.home.lucasacchi.net/
2026-04-06 12:20:14 +02:00
Luca Sacchi Ricciardi
894073644f fix: disable HMR in production to fix WebSocket error behind reverse proxy
- Disable HMR when NODE_ENV=production to prevent WebSocket binding errors
- HMR was trying to bind to external IP:443 which is not available in Docker
- App now works correctly behind HTTPS reverse proxy
2026-04-03 18:55:21 +02:00
Luca Sacchi Ricciardi
26879acba4 feat: add production configuration with environment variables
- 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
2026-04-03 18:49:53 +02:00
Luca Sacchi Ricciardi
92217897ca fix: standardize project name to LogWhispererAI (no space)
- Replace all occurrences of 'LogWhisperer AI' with 'LogWhispererAI'
- Fix 47 instances across 30 files including:
  - Documentation (README, PRD, specs, docs)
  - Frontend components (Footer, Navbar, Hero, etc.)
  - Backend files (Dockerfile, server.js)
  - Workflow files (n8n, bash scripts)
  - Configuration files (AGENTS.md, LICENSE)

Ensures consistent branding across the entire codebase.
2026-04-03 17:07:35 +02:00
Luca Sacchi Ricciardi
8eb7dfb00e feat: add fake-backend mock API server for frontend development
Create mock backend to simulate AI responses for UI development:

Backend Implementation:
- tools/fake-backend/server.js: Express server with CORS
- POST /api/analyze: Accepts log, returns mock AI analysis with 1.5s delay
- GET /health: Health check endpoint
- Pattern matching for different log types (PostgreSQL, Nginx, Node.js, Disk)
- Error handling: 400 for empty payload, 500 for server errors
- Mock responses for common errors (OOM, 502, connection refused, disk full)

Container Setup:
- Dockerfile: Node.js 20 Alpine container
- docker-compose.yml: Added fake-backend service on port 3000
- Health checks for both frontend and backend services
- Environment variable VITE_API_URL for frontend

Frontend Integration:
- InteractiveDemo.tsx: Replaced static data with real fetch() calls
- API_URL configurable via env var (default: http://localhost:3000)
- Error handling with user-friendly messages
- Shows backend URL in demo section
- Maintains loading states and UI feedback

Documentation:
- docs/tools_fake_backend.md: Complete usage guide
- README.md: Updated with tools/fake-backend structure and usage

Development Workflow:
1. docker compose up -d (starts both frontend and backend)
2. Frontend calls http://fake-backend:3000/api/analyze
3. Backend returns realistic mock responses
4. No OpenRouter API costs during development

Safety First:
- No real API calls during development
- Isolated mock logic in dedicated tool
- Easy switch to real backend by changing URL
- CORS enabled only for development

Refs: Sprint 4 preparation, API development workflow
2026-04-03 16:57:14 +02:00
41 changed files with 2142 additions and 149 deletions

41
.env Normal file
View File

@@ -0,0 +1,41 @@
# LogWhispererAI - Production Environment Configuration
# Generated for deployment with reverse proxy
# Date: 2026-04-03
# ============================================
# FRONTEND CONFIGURATION (VITE_* variables)
# These are exposed to the browser
# ============================================
# Backend API URL - HTTPS endpoint via reverse proxy
VITE_API_URL=https://srv-logwhispererai.lab.home.lucasacchi.net
# Webhook base URL - public HTTPS endpoint for webhooks
VITE_WEBHOOK_BASE_URL=https://logwhispererai.lab.home.lucasacchi.net/webhook
# Install script URL - public HTTPS endpoint for install script
VITE_INSTALL_SCRIPT_URL=https://logwhispererai.lab.home.lucasacchi.net/install.sh
# Application identification
VITE_APP_NAME=LogWhispererAI
# Application public URL - main frontend domain
VITE_APP_URL=https://logwhispererai.lab.home.lucasacchi.net
# ============================================
# BACKEND CONFIGURATION
# ============================================
# CORS Origins - restrict to frontend domain for security
# Only requests from this origin will be accepted by the backend
CORS_ORIGINS=https://logwhispererai.lab.home.lucasacchi.net
# Webhook base URL - used for generating webhook URLs
# This should match VITE_WEBHOOK_BASE_URL
WEBHOOK_BASE_URL=https://logwhispererai.lab.home.lucasacchi.net/webhook
# API delay simulation (milliseconds) - simulates AI processing time
DELAY_MS=1500
# Node environment
NODE_ENV=production

View File

@@ -1,22 +1,64 @@
# LogWhisperer AI - Environment Variables # LogWhispererAI - Environment Variables
# ============================================
# FRONTEND CONFIGURATION (VITE_* variables)
# These are exposed to the browser
# ============================================
# Backend API URL - where the frontend will make API calls
# Example: https://srv-logwhispererai.lab.home.lucasacchi.net
VITE_API_URL=http://localhost:3001
# Webhook base URL - used for displaying webhook URLs to users
# Example: https://logwhispererai.lab.home.lucasacchi.net/webhook
VITE_WEBHOOK_BASE_URL=http://localhost:3001/webhook
# Install script URL - the curl command shown to users
# Example: https://logwhispererai.lab.home.lucasacchi.net/install.sh
VITE_INSTALL_SCRIPT_URL=http://localhost:3001/install.sh
# Application identification
VITE_APP_NAME=LogWhispererAI
VITE_APP_URL=http://localhost:5173
# ============================================
# BACKEND CONFIGURATION
# ============================================
# CORS Origins - comma-separated list of allowed frontend origins
# Use '*' for development, set specific domains for production
# Example: https://logwhispererai.lab.home.lucasacchi.net
CORS_ORIGINS=*
# Webhook base URL - used for generating webhook URLs
# Example: https://logwhispererai.lab.home.lucasacchi.net/webhook
WEBHOOK_BASE_URL=https://logwhisperer.ai/webhook
# API delay simulation (milliseconds)
DELAY_MS=1500
# Node environment
NODE_ENV=development
# ============================================
# OPTIONAL: Third-party Services
# ============================================
# Telegram Bot Configuration # Telegram Bot Configuration
# Ottieni questi valori seguendo le istruzioni in docs/telegram_setup.md # TELEGRAM_BOT_TOKEN=your_bot_token_here
TELEGRAM_BOT_TOKEN=your_bot_token_here # TELEGRAM_CHAT_ID=your_chat_id_here
TELEGRAM_CHAT_ID=your_chat_id_here
# n8n Configuration # n8n Configuration
N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/logwhisperer # N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/logwhisperer
# AI Provider Configuration # AI Provider Configuration
OPENAI_API_KEY=your_openai_api_key_here # OPENAI_API_KEY=your_openai_api_key_here
# oppure # ANTHROPIC_API_KEY=your_anthropic_api_key_here
ANTHROPIC_API_KEY=your_anthropic_api_key_here
# Supabase Configuration (per autenticazione e database) # Supabase Configuration (per autenticazione e database)
SUPABASE_URL=https://your-project.supabase.co # SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your_anon_key_here # SUPABASE_ANON_KEY=your_anon_key_here
# Stripe Configuration (per pagamenti) # Stripe Configuration (per pagamenti)
STRIPE_SECRET_KEY=sk_test_your_key_here # STRIPE_SECRET_KEY=sk_test_your_key_here
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here # STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here

View File

@@ -7,7 +7,7 @@ read: true
@n8n-specialist @n8n-specialist
Sei un esperto di automazione n8n. Il tuo compito è interfacciare LogWhisperer AI con l'istanza Docker su 192.168.254.12. Sei un esperto di automazione n8n. Il tuo compito è interfacciare LogWhispererAI con l'istanza Docker su 192.168.254.12.
Focus Operativo Focus Operativo

View File

@@ -1,4 +1,4 @@
# LogWhisperer AI - Agent Rules # LogWhispererAI - Agent Rules
Regole operative per gli agenti AI che collaborano su questo progetto. Regole operative per gli agenti AI che collaborano su questo progetto.

View File

@@ -20,11 +20,11 @@ Nessuna Modifica: È vietato modificare, alterare, adattare, tradurre, decompila
Uso Non Autorizzato: È vietato vendere, noleggiare, dare in locazione, concedere in licenza o utilizzare il software per scopi commerciali senza una licenza specifica rilasciata dall'autore. Uso Non Autorizzato: È vietato vendere, noleggiare, dare in locazione, concedere in licenza o utilizzare il software per scopi commerciali senza una licenza specifica rilasciata dall'autore.
Tutela del Marchio: I nomi "LogWhisperer AI", "Sacchi's Server Sentinel" e il "Metodo Sacchi" sono marchi e metodologie di proprietà dell'autore. Tutela del Marchio: I nomi "LogWhispererAI", "Sacchi's Server Sentinel" e il "Metodo Sacchi" sono marchi e metodologie di proprietà dell'autore.
Legge Applicabile e Foro Competente Legge Applicabile e Foro Competente
Il presente accordo è regolato dalla legge italiana. Per qualsiasi controversia derivante dall'interpretazione, esecuzione o risoluzione della presente licenza e per ogni disputa relativa al progetto "LogWhisperer AI", sarà competente in via esclusiva il Foro di Milano, Italia. Il presente accordo è regolato dalla legge italiana. Per qualsiasi controversia derivante dall'interpretazione, esecuzione o risoluzione della presente licenza e per ogni disputa relativa al progetto "LogWhispererAI", sarà competente in via esclusiva il Foro di Milano, Italia.
Contatti Contatti

View File

@@ -2,6 +2,10 @@
> **UVP:** Il DevOps tascabile che traduce i crash del tuo server e ti dice l'esatto comando per risolverli in sicurezza, senza farti perdere ore su StackOverflow. > **UVP:** Il DevOps tascabile che traduce i crash del tuo server e ti dice l'esatto comando per risolverli in sicurezza, senza farti perdere ore su StackOverflow.
🌐 **Demo Online:** [https://logwhispererai.lab.home.lucasacchi.net/](https://logwhispererai.lab.home.lucasacchi.net/)
**mockupAWS - Backend Profiler & Cost Estimator** [https://gitea.lab.home.lucasacchi.net/lucasacchi/mockupAWS](https://gitea.lab.home.lucasacchi.net/lucasacchi/mockupAWS)
[![Tests](https://img.shields.io/badge/tests-12%2F12%20passing-brightgreen)]() [![Tests](https://img.shields.io/badge/tests-12%2F12%20passing-brightgreen)]()
[![Version](https://img.shields.io/badge/version-0.1.0-blue)]() [![Version](https://img.shields.io/badge/version-0.1.0-blue)]()
[![Sprint](https://img.shields.io/badge/sprint-2%20completed-success)]() [![Sprint](https://img.shields.io/badge/sprint-2%20completed-success)]()
@@ -10,7 +14,7 @@
🎯 Visione del Progetto 🎯 Visione del Progetto
LogWhisperer AI trasforma i log di sistema e database spesso incomprensibili in alert azionabili descritti in "plain language". È pensato per piccole web agency e freelance che gestiscono infrastrutture (AWS, DigitalOcean, VPS) senza avere un sistemista senior dedicato. LogWhispererAI trasforma i log di sistema e database spesso incomprensibili in alert azionabili descritti in "plain language". È pensato per piccole web agency e freelance che gestiscono infrastrutture (AWS, DigitalOcean, VPS) senza avere un sistemista senior dedicato.
## 🚀 Stato di Sviluppo ## 🚀 Stato di Sviluppo
@@ -237,6 +241,11 @@ LogWhispererAI/
├── tests/ ├── tests/
│ ├── __init__.py │ ├── __init__.py
│ └── test_logwhisperer.py # Test suite Python │ └── test_logwhisperer.py # Test suite Python
├── tools/
│ └── fake-backend/ # Mock API server per sviluppo frontend
│ ├── server.js # Server Express mock
│ ├── Dockerfile # Containerizzazione
│ └── README.md # Documentazione tool
└── .opencode/ └── .opencode/
├── opencode.json # Configurazione MCP servers ├── opencode.json # Configurazione MCP servers
├── agents/ # Configurazioni agenti individuali ├── agents/ # Configurazioni agenti individuali
@@ -256,6 +265,31 @@ LogWhispererAI/
└── context7_documentation_retrivial/ └── context7_documentation_retrivial/
``` ```
## 🛠️ Tools di Sviluppo
### Fake Backend (Mock API)
Per sviluppare e testare il frontend senza dipendere dal backend reale:
```bash
# Avvia il mock API server
cd tools/fake-backend
npm install
node server.js
# Oppure con Docker
docker compose up fake-backend -d
```
**Endpoint:** `http://localhost:3000/api/analyze`
Simula le risposte AI con delay di 1.5s. Utile per:
- Sviluppo UI offline
- Testing senza costi API
- Demo senza dipendenze esterne
Vedi `docs/tools_fake_backend.md` per documentazione completa.
## ⚖️ Licenza e Note Legali ## ⚖️ Licenza e Note Legali
Questo software è **proprietà riservata** di Luca Sacchi Ricciardi. Questo software è **proprietà riservata** di Luca Sacchi Ricciardi.

View File

@@ -0,0 +1,23 @@
# Docker Compose Override - Local Development Configuration
# This file is automatically loaded by Docker Compose and overrides docker-compose.yml
# Use this for local development settings without modifying the main docker-compose.yml
#
# Usage: docker compose up -d (automatically loads this file)
#
# To use production configuration, create a .env file and run:
# docker compose -f docker-compose.yml up -d
services:
frontend:
environment:
# Development defaults - override these in .env file for production
- VITE_API_URL=http://192.168.254.79:3001
- VITE_WEBHOOK_BASE_URL=http://192.168.254.79:3001/webhook
- VITE_INSTALL_SCRIPT_URL=http://192.168.254.79:3001/install.sh
- VITE_APP_URL=http://192.168.254.79:5173
fake-backend:
environment:
# Development defaults
- CORS_ORIGINS=*
- WEBHOOK_BASE_URL=http://192.168.254.79:3001/webhook

31
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,31 @@
# Docker Compose - Production Configuration
# Usage with reverse proxy:
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
#
# Required: Create a .env file with production values before running
services:
frontend:
# In production, don't mount source volumes (use built image)
volumes:
- node_modules:/app/node_modules
environment:
# Production API URLs - these must be set in .env file
- VITE_API_URL=${VITE_API_URL}
- VITE_WEBHOOK_BASE_URL=${VITE_WEBHOOK_BASE_URL}
- VITE_INSTALL_SCRIPT_URL=${VITE_INSTALL_SCRIPT_URL}
- VITE_APP_URL=${VITE_APP_URL}
- NODE_ENV=production
fake-backend:
environment:
# Production CORS - restrict to frontend domain
- CORS_ORIGINS=${CORS_ORIGINS}
# Production webhook URL
- WEBHOOK_BASE_URL=${WEBHOOK_BASE_URL}
# Production settings
- NODE_ENV=production
- DELAY_MS=${DELAY_MS:-1500}

View File

@@ -1,6 +1,19 @@
# Docker Compose - LogWhisperer AI Development Environment # Docker Compose - LogWhispererAI Development Environment
# Usage: docker compose up -d # Usage: docker compose up -d
# Access: http://localhost:5173 # Access Frontend: http://localhost:5173
# Access Fake Backend API: http://localhost:3001
#
# For production deployment with reverse proxy:
# 1. Copy .env.example to .env and customize values
# 2. Run: docker compose up -d
#
# Required environment variables for production:
# VITE_API_URL=https://srv-logwhispererai.lab.home.lucasacchi.net
# VITE_WEBHOOK_BASE_URL=https://logwhispererai.lab.home.lucasacchi.net/webhook
# VITE_INSTALL_SCRIPT_URL=https://logwhispererai.lab.home.lucasacchi.net/install.sh
# VITE_APP_URL=https://logwhispererai.lab.home.lucasacchi.net
# CORS_ORIGINS=https://logwhispererai.lab.home.lucasacchi.net
# WEBHOOK_BASE_URL=https://logwhispererai.lab.home.lucasacchi.net/webhook
services: services:
frontend: frontend:
@@ -16,10 +29,42 @@ services:
# Use named volume for node_modules to avoid conflicts with host # Use named volume for node_modules to avoid conflicts with host
- node_modules:/app/node_modules - node_modules:/app/node_modules
environment: environment:
- NODE_ENV=development # Node environment
- NODE_ENV=${NODE_ENV:-development}
- CHOKIDAR_USEPOLLING=true - CHOKIDAR_USEPOLLING=true
# ============================================
# FRONTEND CONFIGURATION (VITE_* variables)
# These are exposed to the browser
# ============================================
# Backend API URL - where the frontend will make API calls
# Default: http://fake-backend:3000 (internal Docker network)
# Production: https://srv-logwhispererai.lab.home.lucasacchi.net
- VITE_API_URL=${VITE_API_URL:-http://fake-backend:3000}
# Webhook base URL - used for displaying webhook URLs to users
# Default: http://localhost:3001/webhook
# Production: https://logwhispererai.lab.home.lucasacchi.net/webhook
- VITE_WEBHOOK_BASE_URL=${VITE_WEBHOOK_BASE_URL:-http://localhost:3001/webhook}
# Install script URL - the curl command shown to users
# Default: http://localhost:3001/install.sh
# Production: https://logwhispererai.lab.home.lucasacchi.net/install.sh
- VITE_INSTALL_SCRIPT_URL=${VITE_INSTALL_SCRIPT_URL:-http://localhost:3001/install.sh}
# Application identification
- VITE_APP_NAME=${VITE_APP_NAME:-LogWhispererAI}
# Application public URL
# Default: http://localhost:5173
# Production: https://logwhispererai.lab.home.lucasacchi.net
- VITE_APP_URL=${VITE_APP_URL:-http://localhost:5173}
# Ensure container restarts on failure # Ensure container restarts on failure
restart: unless-stopped restart: unless-stopped
depends_on:
- fake-backend
# Health check to verify the service is running # Health check to verify the service is running
healthcheck: healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5173"] test: ["CMD", "wget", "-q", "--spider", "http://localhost:5173"]
@@ -28,6 +73,47 @@ services:
retries: 3 retries: 3
start_period: 40s start_period: 40s
fake-backend:
build:
context: ./tools/fake-backend
dockerfile: Dockerfile
container_name: logwhisperer-fake-backend
ports:
- "3001:3000"
environment:
# Server port (internal)
- PORT=3000
# API delay simulation (milliseconds)
- DELAY_MS=${DELAY_MS:-1500}
# Node environment
- NODE_ENV=${NODE_ENV:-production}
# ============================================
# BACKEND CONFIGURATION
# ============================================
# CORS origins - comma-separated list of allowed frontend origins
# Use '*' for development (allows all origins)
# Production: set to your frontend domain for security
# Example: https://logwhispererai.lab.home.lucasacchi.net
- CORS_ORIGINS=${CORS_ORIGINS:-*}
# Webhook base URL - used for generating webhook URLs
# This should be the public URL that users see
# Default: https://logwhisperer.ai/webhook
# Production: https://logwhispererai.lab.home.lucasacchi.net/webhook
- WEBHOOK_BASE_URL=${WEBHOOK_BASE_URL:-https://logwhisperer.ai/webhook}
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"]
interval: 30s
timeout: 3s
retries: 3
start_period: 5s
volumes: volumes:
node_modules: node_modules:
driver: local driver: local

View File

@@ -1,6 +1,6 @@
# LogWhisperer AI - Agent Staff Catalog # LogWhispererAI - Agent Staff Catalog
Catalogo completo degli agenti AI specializzati che collaborano sul progetto LogWhisperer AI. Catalogo completo degli agenti AI specializzati che collaborano sul progetto LogWhispererAI.
--- ---
@@ -211,7 +211,7 @@ Mantiene aggiornata la documentazione e il changelog.
Esperto in automazione e orchestrazione n8n. Esperto in automazione e orchestrazione n8n.
**Responsabilità:** **Responsabilità:**
- Interfacciare LogWhisperer AI con l'istanza Docker su 192.168.254.12 - Interfacciare LogWhispererAI con l'istanza Docker su 192.168.254.12
- Configurazione e testing dei Webhook di ricezione log - Configurazione e testing dei Webhook di ricezione log
- Monitorare esecuzioni per garantire applicazione "Metodo Sacchi" nei prompt LLM - Monitorare esecuzioni per garantire applicazione "Metodo Sacchi" nei prompt LLM
- Troubleshooting della connessione tra script Bash locale e API n8n - Troubleshooting della connessione tra script Bash locale e API n8n

View File

@@ -1,4 +1,4 @@
Ottimo progetto. Per trasformare **LogWhisperer AI** in un prodotto reale in 4 giorni usando **OpenCode.ai**, dobbiamo configurare l'ambiente in modo che l'agente non si limiti a scrivere codice, ma agisca come un vero team di sviluppo (Tech Lead + Developer + QA). Ottimo progetto. Per trasformare **LogWhispererAI** in un prodotto reale in 4 giorni usando **OpenCode.ai**, dobbiamo configurare l'ambiente in modo che l'agente non si limiti a scrivere codice, ma agisca come un vero team di sviluppo (Tech Lead + Developer + QA).
Ecco come configurare OpenCode.ai seguendo la tua metodologia **Spec-Driven**, **TDD** e il **Metodo Sacchi**. Ecco come configurare OpenCode.ai seguendo la tua metodologia **Spec-Driven**, **TDD** e il **Metodo Sacchi**.

131
docs/docker-compose.md Normal file
View File

@@ -0,0 +1,131 @@
# Docker Compose Configuration
Questa directory contiene diverse configurazioni Docker Compose per vari ambienti.
## File Disponibili
| File | Scopo | Uso |
|------|-------|-----|
| `docker-compose.yml` | Configurazione base con variabili d'ambiente | Sviluppo e produzione |
| `docker-compose.override.yml` | Override per sviluppo locale | Caricato automaticamente |
| `docker-compose.prod.yml` | Configurazione produzione | Da usare con `-f` |
## Variabili d'Ambiente
Tutte le variabili sono configurabili tramite file `.env` o variabili di sistema.
### Frontend (VITE_*)
| Variabile | Default | Descrizione |
|-----------|---------|-------------|
| `VITE_API_URL` | `http://fake-backend:3000` | URL del backend API |
| `VITE_WEBHOOK_BASE_URL` | `http://localhost:3001/webhook` | Base URL webhook |
| `VITE_INSTALL_SCRIPT_URL` | `http://localhost:3001/install.sh` | URL script installazione |
| `VITE_APP_NAME` | `LogWhispererAI` | Nome applicazione |
| `VITE_APP_URL` | `http://localhost:5173` | URL pubblico app |
### Backend
| Variabile | Default | Descrizione |
|-----------|---------|-------------|
| `PORT` | `3000` | Porta server (interna) |
| `DELAY_MS` | `1500` | Delay simulazione API |
| `NODE_ENV` | `production` | Ambiente Node |
| `CORS_ORIGINS` | `*` | Origini CORS consentite |
| `WEBHOOK_BASE_URL` | `https://logwhisperer.ai/webhook` | Base URL webhook |
## Utilizzo
### Sviluppo Locale (default)
```bash
# Le variabili di docker-compose.override.yml vengono usate automaticamente
docker compose up -d
```
### Sviluppo con configurazione personalizzata
```bash
# Crea un file .env nella root del progetto
cat > .env << 'EOF'
VITE_API_URL=http://192.168.254.79:3001
VITE_WEBHOOK_BASE_URL=http://192.168.254.79:3001/webhook
CORS_ORIGINS=*
EOF
# Avvia con le variabili dal file .env
docker compose up -d
```
### Produzione con Reverse Proxy
```bash
# 1. Crea il file .env con i valori di produzione
cat > .env << 'EOF'
VITE_API_URL=https://srv-logwhispererai.lab.home.lucasacchi.net
VITE_WEBHOOK_BASE_URL=https://logwhispererai.lab.home.lucasacchi.net/webhook
VITE_INSTALL_SCRIPT_URL=https://logwhispererai.lab.home.lucasacchi.net/install.sh
VITE_APP_URL=https://logwhispererai.lab.home.lucasacchi.net
CORS_ORIGINS=https://logwhispererai.lab.home.lucasacchi.net
WEBHOOK_BASE_URL=https://logwhispererai.lab.home.lucasacchi.net/webhook
NODE_ENV=production
EOF
# 2. Avvia con la configurazione di produzione
# (ignora docker-compose.override.yml)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
```
### Solo Backend
```bash
docker compose up fake-backend -d
```
### Solo Frontend
```bash
docker compose up frontend -d
```
## Verifica Configurazione
```bash
# Controlla le variabili caricate
docker compose config
# Verifica il backend
curl https://srv-logwhispererai.lab.home.lucasacchi.net/health
# Verifica la generazione webhook
curl -X POST https://srv-logwhispererai.lab.home.lucasacchi.net/api/webhook
```
## Troubleshooting
### Cambiare le variabili
Se modifichi il file `.env`, devi ricreare i container:
```bash
docker compose down
docker compose up -d
```
### Vedere le variabili in uso
```bash
# Frontend
docker exec logwhisperer-frontend-dev env | grep VITE
# Backend
docker exec logwhisperer-fake-backend env | grep -E '(CORS|WEBHOOK)'
```
### Reset alla configurazione default
```bash
docker compose down
rm .env # Rimuovi configurazione personalizzata
docker compose up -d # Userà i valori default
```

View File

@@ -1,6 +1,6 @@
# Piano d'Azione per lo Sviluppo della Landing Page # Piano d'Azione per lo Sviluppo della Landing Page
**Obiettivo:** Creare una landing page di alta qualità che comunichi efficacemente l'UVP di LogWhisperer AI e converta i visitatori in utenti attivi. **Obiettivo:** Creare una landing page di alta qualità che comunichi efficacemente l'UVP di LogWhispererAI e converta i visitatori in utenti attivi.
## 🎯 UVP da Comunicare ## 🎯 UVP da Comunicare
> *"Il DevOps tascabile che traduce i crash del tuo server e ti dice l'esatto comando per risolverli"* > *"Il DevOps tascabile che traduce i crash del tuo server e ti dice l'esatto comando per risolverli"*

View File

@@ -1,6 +1,6 @@
# Git History - LogWhisperer AI # Git History - LogWhispererAI
**Repository:** LogWhisperer AI **Repository:** LogWhispererAI
**Branch Principale:** `main` **Branch Principale:** `main`
**Ultimo Aggiornamento:** 2026-04-02 **Ultimo Aggiornamento:** 2026-04-02
**Commit Corrente:** `88cfe9a` **Commit Corrente:** `88cfe9a`
@@ -18,7 +18,7 @@
## Panoramica ## Panoramica
Questo documento traccia la storia dei commit del progetto LogWhisperer AI. Viene aggiornato periodicamente per riflettere l'evoluzione del codebase. Questo documento traccia la storia dei commit del progetto LogWhispererAI. Viene aggiornato periodicamente per riflettere l'evoluzione del codebase.
### Convenzioni di Commit ### Convenzioni di Commit

View File

@@ -1,5 +1,5 @@
# Product Requirements Document (PRD) - MVP # Product Requirements Document (PRD) - MVP
## Progetto: LogWhisperer AI (aka Sacchi's Server Sentinel) ## Progetto: LogWhispererAI (aka Sacchi's Server Sentinel)
**Status:** 🟢 MVP Active Development - Sprint 1 Completed **Status:** 🟢 MVP Active Development - Sprint 1 Completed
**Last Updated:** 2026-04-02 **Last Updated:** 2026-04-02

View File

@@ -4,7 +4,7 @@ Here is the English translation:
**PROMPT TO INSERT INTO THE AI:** **PROMPT TO INSERT INTO THE AI:**
*"Act as a Tech Lead and Project Manager expert in Lean Startup and Micro-SaaS methodologies. I have provided you with the Product Requirements Document (PRD) for the 'LogWhisperer AI' project. *"Act as a Tech Lead and Project Manager expert in Lean Startup and Micro-SaaS methodologies. I have provided you with the Product Requirements Document (PRD) for the 'LogWhispererAI' project.
Our goal is to launch this MVP to market in just 4 days ('fail fast, fail cheap' approach) using No-Code/Low-Code logic wherever possible to save time. Our goal is to launch this MVP to market in just 4 days ('fail fast, fail cheap' approach) using No-Code/Low-Code logic wherever possible to save time.
Your task is to analyze the PRD and generate a development strategy ('Spec-driven development') structured as follows:* Your task is to analyze the PRD and generate a development strategy ('Spec-driven development') structured as follows:*

129
docs/reverse_proxy_setup.md Normal file
View File

@@ -0,0 +1,129 @@
# Configurazione con Reverse Proxy
Questa guida spiega come configurare LogWhispererAI con un reverse proxy SSL.
## Scenario
Hai configurato:
- **Frontend**: `https://logwhispererai.lab.home.lucasacchi.net``http://192.168.254.79:5173`
- **Backend**: `https://srv-logwhispererai.lab.home.lucasacchi.net``http://192.168.254.79:3001`
## Configurazione
### 1. Crea il file di configurazione
Crea un file `.env` nella root del progetto:
```bash
cp .env.example .env
```
Modifica il file `.env` con i tuoi valori:
```env
# Frontend Configuration
VITE_API_URL=https://srv-logwhispererai.lab.home.lucasacchi.net
VITE_WEBHOOK_BASE_URL=https://logwhispererai.lab.home.lucasacchi.net/webhook
VITE_INSTALL_SCRIPT_URL=https://logwhispererai.lab.home.lucasacchi.net/install.sh
VITE_APP_NAME=LogWhispererAI
VITE_APP_URL=https://logwhispererai.lab.home.lucasacchi.net
# Backend Configuration
CORS_ORIGINS=https://logwhispererai.lab.home.lucasacchi.net
WEBHOOK_BASE_URL=https://logwhispererai.lab.home.lucasacchi.net/webhook
DELAY_MS=1500
NODE_ENV=production
```
### 2. Avvia i servizi
```bash
# Ferma eventuali container esistenti
docker compose down
# Avvia con le nuove variabili
docker compose up -d
```
### 3. Verifica la configurazione
Testa i vari endpoint:
```bash
# Health check backend
curl https://srv-logwhispererai.lab.home.lucasacchi.net/health
# Genera un webhook
curl -X POST https://srv-logwhispererai.lab.home.lucasacchi.net/api/webhook
# Analizza un log
curl -X POST https://srv-logwhispererai.lab.home.lucasacchi.net/api/analyze \
-H "Content-Type: application/json" \
-d '{"log": "FATAL: out of memory"}'
```
### 4. Verifica il frontend
Apri `https://logwhispererai.lab.home.lucasacchi.net` nel browser e verifica che:
1. La demo interattiva funzioni (chiama il backend corretto)
2. Lo step 2 dell'onboarding generi webhook con l'URL corretto
## Variabili d'Ambiente
### Frontend (VITE_*)
| Variabile | Descrizione | Esempio |
|-----------|-------------|---------|
| `VITE_API_URL` | URL del backend API | `https://srv-logwhispererai.lab.home.lucasacchi.net` |
| `VITE_WEBHOOK_BASE_URL` | Base URL per i webhook | `https://logwhispererai.lab.home.lucasacchi.net/webhook` |
| `VITE_INSTALL_SCRIPT_URL` | URL dello script di installazione | `https://logwhispererai.lab.home.lucasacchi.net/install.sh` |
| `VITE_APP_NAME` | Nome dell'applicazione | `LogWhispererAI` |
| `VITE_APP_URL` | URL pubblico dell'app | `https://logwhispererai.lab.home.lucasacchi.net` |
### Backend
| Variabile | Descrizione | Esempio |
|-----------|-------------|---------|
| `CORS_ORIGINS` | Origini CORS consentite (comma-separated) | `https://logwhispererai.lab.home.lucasacchi.net` |
| `WEBHOOK_BASE_URL` | Base URL per i webhook generati | `https://logwhispererai.lab.home.lucasacchi.net/webhook` |
| `DELAY_MS` | Delay simulato API (ms) | `1500` |
| `NODE_ENV` | Ambiente Node | `production` |
## File Configurati
- `frontend/.env` - Configurazione frontend per produzione
- `frontend/.env.development` - Configurazione per sviluppo locale
- `frontend/vite.config.ts` - Allow hosts per Vite
- `tools/fake-backend/server.js` - Supporto CORS dinamico e webhook URL configurabili
- `docker-compose.yml` - Passaggio variabili ai container
## Troubleshooting
### Errore "Blocked request" di Vite
Se vedi questo errore:
```
Blocked request. This host ("logwhispererai.lab.home.lucasacchi.net") is not allowed.
```
Aggiungi il dominio a `frontend/vite.config.ts`:
```typescript
server: {
allowedHosts: [
'logwhispererai.lab.home.lucasacchi.net',
],
}
```
### Errore CORS
Se il browser blocca le richieste API:
1. Verifica che `CORS_ORIGINS` includa il dominio del frontend
2. Ricostruisci il backend: `docker compose up fake-backend --build -d`
### Webhook URL errati
Se i webhook generati hanno URL sbagliati:
1. Verifica `WEBHOOK_BASE_URL` nel backend
2. Verifica `VITE_WEBHOOK_BASE_URL` nel frontend
3. Ricostruisci entrambi i servizi

View File

@@ -1,5 +1,5 @@
# Project Review - Sprint 1 # Project Review - Sprint 1
## LogWhisperer AI - Log Ingestion ## LogWhispererAI - Log Ingestion
**Data Review:** 2026-04-02 **Data Review:** 2026-04-02
**Sprint:** 1 - Log Ingestion Script **Sprint:** 1 - Log Ingestion Script
@@ -430,7 +430,7 @@ Tutti i deliverable dello Sprint 1 sono stati prodotti e verificati con successo
--- ---
**Review Condotta da:** Agent Staff LogWhisperer AI **Review Condotta da:** Agent Staff LogWhispererAI
**Data:** 2026-04-02 **Data:** 2026-04-02
**Prossima Review:** Post-Sprint 2 **Prossima Review:** Post-Sprint 2

View File

@@ -1,6 +1,6 @@
# Roadmap & Suggerimenti di Sviluppo # Roadmap & Suggerimenti di Sviluppo
> **Documento Living** - Questo file raccoglie idee, suggerimenti e potenziali nuove funzionalità per LogWhisperer AI. > **Documento Living** - Questo file raccoglie idee, suggerimenti e potenziali nuove funzionalità per LogWhispererAI.
> >
> Ultimo aggiornamento: 2026-04-03 > Ultimo aggiornamento: 2026-04-03
@@ -289,4 +289,4 @@ Hai un'idea? Aggiungila a questo documento seguendo il formato:
--- ---
*Documento mantenuto dal team LogWhisperer AI* *Documento mantenuto dal team LogWhispererAI*

View File

@@ -1,4 +1,4 @@
# 🤖 Staff di Agenti AI - LogWhisperer AI # 🤖 Staff di Agenti AI - LogWhispererAI
Questo documento definisce i ruoli, le responsabilità e le configurazioni per i sotto-agenti da utilizzare in OpenCode.ai per garantire un workflow "Spec-Driven" e "Safety First". Questo documento definisce i ruoli, le responsabilità e le configurazioni per i sotto-agenti da utilizzare in OpenCode.ai per garantire un workflow "Spec-Driven" e "Safety First".

View File

@@ -10,7 +10,7 @@
## 1. Overview ## 1. Overview
### 1.1 Scopo ### 1.1 Scopo
L'AI Processing Pipeline è il cuore intelligente di LogWhisperer AI. Trasforma log grezzi di sistema in insight azionabili utilizzando OpenRouter per accedere a modelli AI (GPT-4o-mini) con fallback automatico e costi ottimizzati. L'AI Processing Pipeline è il cuore intelligente di LogWhispererAI. Trasforma log grezzi di sistema in insight azionabili utilizzando OpenRouter per accedere a modelli AI (GPT-4o-mini) con fallback automatico e costi ottimizzati.
### 1.2 Flusso Dati ### 1.2 Flusso Dati
@@ -56,7 +56,7 @@ Il prompt di sistema incorpora esplicitamente i tre pilastri del Metodo Sacchi:
### 2.2 System Prompt Completo ### 2.2 System Prompt Completo
```javascript ```javascript
const SYSTEM_PROMPT = `Sei LogWhisperer AI, un assistente DevOps esperto specializzato nell'analisi di log di sistema. const SYSTEM_PROMPT = `Sei LogWhispererAI, un assistente DevOps esperto specializzato nell'analisi di log di sistema.
## MISSIONE ## MISSIONE
Analizza i log ricevuti e fornisci insight azionabili in italiano, semplice e chiaro. Analizza i log ricevuti e fornisci insight azionabili in italiano, semplice e chiaro.
@@ -305,7 +305,7 @@ const SITE_URL = process.env.OPENROUTER_SITE_URL || 'https://logwhisperer.ai';
const APP_NAME = process.env.OPENROUTER_APP_NAME || 'LogWhispererAI'; const APP_NAME = process.env.OPENROUTER_APP_NAME || 'LogWhispererAI';
// System Prompt (incollare qui il SYSTEM_PROMPT completo) // System Prompt (incollare qui il SYSTEM_PROMPT completo)
const SYSTEM_PROMPT = `Sei LogWhisperer AI...`; const SYSTEM_PROMPT = `Sei LogWhispererAI...`;
// Input dal nodo precedente // Input dal nodo precedente
const inputData = $input.first().json; const inputData = $input.first().json;

View File

@@ -1,6 +1,6 @@
# Sprint 1 Verification Report # Sprint 1 Verification Report
**Progetto:** LogWhisperer AI **Progetto:** LogWhispererAI
**Sprint:** 1 - Log Ingestion Script **Sprint:** 1 - Log Ingestion Script
**Data Verifica:** 2026-04-02 **Data Verifica:** 2026-04-02
**Verificatore:** OpenCode Agent **Verificatore:** OpenCode Agent
@@ -38,7 +38,7 @@
## 1. Overview ## 1. Overview
Questo documento certifica il completamento dello Sprint 1 del progetto LogWhisperer AI. Lo sprint aveva come obiettivo la creazione di uno script Bash per l'ingestion dei log di sistema, seguendo la metodologia TDD (Test-Driven Development) e il Metodo Sacchi. Questo documento certifica il completamento dello Sprint 1 del progetto LogWhispererAI. Lo sprint aveva come obiettivo la creazione di uno script Bash per l'ingestion dei log di sistema, seguendo la metodologia TDD (Test-Driven Development) e il Metodo Sacchi.
--- ---

192
docs/tools_fake_backend.md Normal file
View File

@@ -0,0 +1,192 @@
# Fake Backend Tool
> **Strumento di sviluppo per simulare risposte API AI**
>
> Scopo: Permettere lo sviluppo e il testing del frontend senza dipendere dal backend reale o dalle API di OpenRouter.
---
## 📋 Descrizione
Il `fake-backend` è un server Node.js/Express che simula le risposte dell'API di LogWhispererAI. È progettato per:
- **Sviluppo UI**: Testare l'interfaccia utente con risposte realistiche
- **Demo offline**: Mostrare il prodotto senza connessione internet
- **Testing**: Validare il flusso frontend senza costi API
- **Onboarding**: Permettere ai nuovi sviluppatori di lavorare subito
---
## 🚀 Endpoint API
### POST /api/analyze
Analizza un log e restituisce una risposta AI simulata.
**Request:**
```http
POST http://localhost:3000/api/analyze
Content-Type: application/json
{
"log": "FATAL: database system is out of memory"
}
```
**Response (dopo 1.5s delay):**
```json
{
"success": true,
"analysis": {
"title": "PostgreSQL Out of Memory",
"description": "Il database ha esaurito la memoria disponibile...",
"command": "ps aux | grep postgres | head -5 && free -h",
"isSafe": true,
"note": "Verifica processi Postgres e memoria disponibile."
},
"timestamp": "2026-04-03T10:30:00.000Z"
}
```
**Errori gestiti:**
- `400 Bad Request`: Payload vuoto o malformato
- `500 Internal Error`: Errore server generico
---
## 🛠️ Setup
### Prerequisiti
- Node.js 18+
- npm 9+
### Installazione
```bash
# Entra nella directory
cd tools/fake-backend
# Installa dipendenze
npm install
# Avvia il server
node server.js
```
Il server sarà disponibile su `http://localhost:3000`
---
## 🐳 Docker
Per avviare il fake backend con Docker:
```bash
# Dalla root del progetto
docker compose up fake-backend -d
```
Il servizio sarà esposto sulla porta 3000.
---
## 🔧 Configurazione
Variabili ambiente (opzionali):
```bash
PORT=3000 # Porta server (default: 3000)
DELAY_MS=1500 # Delay simulazione AI (default: 1500ms)
```
---
## 📁 Struttura
```
tools/fake-backend/
├── server.js # Server Express principale
├── package.json # Dipendenze npm
├── Dockerfile # Containerizzazione
└── README.md # Questo file
```
---
## 🔒 Sicurezza
⚠️ **ATTENZIONE**: Questo è uno strumento di sviluppo. NON usarlo in produzione:
- Nessuna autenticazione implementata
- Risposte statiche predefinite
- Nessuna validazione input avanzata
- CORS abilitato per tutte le origini
---
## 🧪 Test Manuale
```bash
# Test endpoint analyze
curl -X POST http://localhost:3000/api/analyze \
-H "Content-Type: application/json" \
-d '{"log": "Error: Connection refused"}'
```
---
## 📝 Note per Sviluppatori
### Aggiungere nuove risposte mock
Modifica l'oggetto `MOCK_RESPONSES` in `server.js`:
```javascript
const MOCK_RESPONSES = {
'errore-specifico': {
title: 'Titolo Problema',
description: 'Descrizione...',
command: 'comando-da-eseguire',
isSafe: true,
note: 'Nota aggiuntiva'
}
};
```
### Pattern matching
Il server cerca keyword nel log e restituisce la risposta appropriata:
- "memory" / "oom" → PostgreSQL OOM
- "connection refused" / "502" → Nginx/Connection error
- Default → Risposta generica
---
## 🤝 Integrazione Frontend
Per usare il fake backend dal frontend React:
```typescript
const analyzeLog = async (log: string) => {
const response = await fetch('http://localhost:3000/api/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ log })
});
return response.json();
};
```
---
## 🔄 Passaggio a Backend Reale
Quando il backend reale è pronto:
1. Aggiorna l'URL in `InteractiveDemo.tsx`
2. Aggiungi autenticazione (JWT/API Key)
3. Rimuovi CORS permissivo
4. Implementa rate limiting
---
*Strumento creato seguendo il principio "Safety First" del Metodo Sacchi*

15
frontend/.env.development Normal file
View File

@@ -0,0 +1,15 @@
# LogWhispererAI - Frontend Environment Variables (Development)
# These variables are exposed to the browser (must start with VITE_)
# Backend API URL
VITE_API_URL=http://192.168.254.79:3001
# Base URL for webhook endpoints
VITE_WEBHOOK_BASE_URL=http://192.168.254.79:3001/webhook
# Install script URL
VITE_INSTALL_SCRIPT_URL=http://192.168.254.79:3001/install.sh
# Application identification
VITE_APP_NAME=LogWhispererAI
VITE_APP_URL=http://192.168.254.79:5173

View File

@@ -1,4 +1,4 @@
# Dockerfile.dev - Development container for LogWhisperer AI Frontend # Dockerfile.dev - Development container for LogWhispererAI Frontend
# Optimized for Node.js with Hot Module Replacement (HMR) # Optimized for Node.js with Hot Module Replacement (HMR)
FROM node:20-alpine FROM node:20-alpine

View File

@@ -53,7 +53,7 @@ export const Footer: React.FC = () => {
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<span className="text-2xl">🌌</span> <span className="text-2xl">🌌</span>
<span className="text-xl font-bold text-white tracking-tight"> <span className="text-xl font-bold text-white tracking-tight">
LogWhisperer AI LogWhispererAI
</span> </span>
</div> </div>
<p className="text-slate-400 text-sm leading-relaxed mb-6"> <p className="text-slate-400 text-sm leading-relaxed mb-6">
@@ -226,7 +226,7 @@ export const Footer: React.FC = () => {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="flex flex-col sm:flex-row items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<p className="text-slate-500 text-sm"> <p className="text-slate-500 text-sm">
© {currentYear} LogWhisperer AI. Tutti i diritti riservati. © {currentYear} LogWhispererAI. Tutti i diritti riservati.
</p> </p>
<p className="text-slate-600 text-xs"> <p className="text-slate-600 text-xs">
Made with by Luca Sacchi Ricciardi Made with by Luca Sacchi Ricciardi

View File

@@ -12,7 +12,7 @@ export const Navbar: React.FC<NavbarProps> = ({ onCtaClick }) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-2xl">🌌</span> <span className="text-2xl">🌌</span>
<span className="text-xl font-bold text-slate-900 tracking-tight"> <span className="text-xl font-bold text-slate-900 tracking-tight">
LogWhisperer AI LogWhispererAI
</span> </span>
</div> </div>
@@ -20,7 +20,7 @@ export const Navbar: React.FC<NavbarProps> = ({ onCtaClick }) => {
<button <button
onClick={onCtaClick} onClick={onCtaClick}
className="px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 text-sm sm:text-base" className="px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 text-sm sm:text-base"
aria-label="Inizia gratis con LogWhisperer AI" aria-label="Inizia gratis con LogWhispererAI"
> >
Inizia Gratis Inizia Gratis
</button> </button>

View File

@@ -49,7 +49,7 @@ export const Hero: React.FC<HeroProps> = ({
<button <button
onClick={onSecondaryCtaClick} onClick={onSecondaryCtaClick}
className="w-full sm:w-auto px-8 py-4 bg-white hover:bg-slate-50 text-slate-700 font-semibold rounded-xl border-2 border-slate-200 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 text-lg" className="w-full sm:w-auto px-8 py-4 bg-white hover:bg-slate-50 text-slate-700 font-semibold rounded-xl border-2 border-slate-200 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 text-lg"
aria-label="Guarda la demo di LogWhisperer AI" aria-label="Guarda la demo di LogWhispererAI"
> >
Guarda la Demo Guarda la Demo
</button> </button>

View File

@@ -1,85 +1,34 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Terminal, Copy, Check, Loader2, AlertCircle, Activity } from 'lucide-react'; import { Terminal, Copy, Check, Loader2, AlertCircle, Activity, Server, Globe, Code } from 'lucide-react';
// Mock data for demo logs // Demo log presets - only the content, not the analysis
const DEMO_LOGS = [ const DEMO_LOGS = [
{ {
id: 'postgres-oom', id: 'postgres-oom',
label: 'PostgreSQL OOM', label: 'PostgreSQL OOM',
icon: <Database className="w-4 h-4" />, icon: Server,
logContent: `FATAL: database system is out of memory logContent: `FATAL: database system is out of memory
DETAIL: Failed on request of size 8192 DETAIL: Failed on request of size 8192
HINT: Check memory usage and limits HINT: Check memory usage and limits
CONTEXT: automatic vacuum of table "public.events"`, CONTEXT: automatic vacuum of table "public.events"`,
analysis: {
title: 'PostgreSQL Out of Memory',
description: 'Il database ha esaurito la memoria disponibile durante un\'operazione di vacuum automatico su una tabella molto grande.',
command: 'ps aux | grep postgres | head -5 && free -h',
isSafe: true,
note: 'Verifica processi Postgres e memoria disponibile. Se necessario, aumenta work_mem o shared_buffers.',
},
}, },
{ {
id: 'nginx-502', id: 'nginx-502',
label: 'Nginx 502 Bad Gateway', label: 'Nginx 502 Bad Gateway',
icon: <Globe className="w-4 h-4" />, icon: Globe,
logContent: `2024/01/15 14:32:15 [error] 1234#1234: *56789 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.100, server: api.example.com, upstream: "127.0.0.1:3000"`, logContent: `2024/01/15 14:32:15 [error] 1234#1234: *56789 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.100, server: api.example.com, upstream: "127.0.0.1:3000"`,
analysis: {
title: 'Nginx 502 - Backend Non Raggiungibile',
description: 'Nginx non riesce a connettersi al backend sulla porta 3000. Probabilmente il servizio è down.',
command: 'sudo systemctl status app-service && netstat -tlnp | grep 3000',
isSafe: true,
note: 'Verifica stato del servizio backend. Se stopped, avvia con: sudo systemctl start app-service',
},
}, },
{ {
id: 'node-exception', id: 'node-exception',
label: 'Node.js Exception', label: 'Node.js Exception',
icon: <Code className="w-4 h-4" />, icon: Code,
logContent: `Error: connect ECONNREFUSED 127.0.0.1:5432 logContent: `Error: connect ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16) at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16)
at emitErrorNT (internal/streams/destroy.js:92:8) at emitErrorNT (internal/streams/destroy.js:92:8)
at processTicksAndRejections (internal/process/task_queues.js:80:21)`, at processTicksAndRejections (internal/process/task_queues.js:80:21)`,
analysis: {
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',
},
}, },
]; ];
// Icon components
function Database(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M3 5V19A9 3 0 0 0 21 19V5" />
<path d="M3 12A9 3 0 0 0 21 12" />
</svg>
);
}
function Globe(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
);
}
function Code(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="16 18 22 12 16 6" />
<polyline points="8 6 2 12 8 18" />
</svg>
);
}
interface LogAnalysis { interface LogAnalysis {
title: string; title: string;
description: string; description: string;
@@ -88,26 +37,61 @@ interface LogAnalysis {
note: string; note: string;
} }
interface ApiResponse {
success: boolean;
analysis: LogAnalysis & { originalLog?: string };
meta?: {
processingTime: string;
model: string;
timestamp: string;
};
error?: string;
message?: string;
}
// Environment configuration
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001';
export const InteractiveDemo: React.FC = () => { export const InteractiveDemo: React.FC = () => {
const [selectedLog, setSelectedLog] = useState<string | null>(null); const [selectedLog, setSelectedLog] = useState<string | null>(null);
const [isAnalyzing, setIsAnalyzing] = useState(false); const [isAnalyzing, setIsAnalyzing] = useState(false);
const [analysis, setAnalysis] = useState<LogAnalysis | null>(null); const [analysis, setAnalysis] = useState<LogAnalysis | null>(null);
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleLogSelect = async (logId: string) => {
const logData = DEMO_LOGS.find((l) => l.id === logId);
if (!logData) return;
const handleLogSelect = (logId: string) => {
setSelectedLog(logId); setSelectedLog(logId);
setIsAnalyzing(true); setIsAnalyzing(true);
setAnalysis(null); setAnalysis(null);
setCopied(false); setCopied(false);
setError(null);
// Simulate AI analysis delay try {
setTimeout(() => { // Real API call to fake-backend
const log = DEMO_LOGS.find((l) => l.id === logId); const response = await fetch(`${API_URL}/api/analyze`, {
if (log) { method: 'POST',
setAnalysis(log.analysis); headers: {
setIsAnalyzing(false); 'Content-Type': 'application/json',
},
body: JSON.stringify({ log: logData.logContent }),
});
const data: ApiResponse = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.message || `HTTP error! status: ${response.status}`);
} }
}, 1500);
setAnalysis(data.analysis);
} catch (err) {
console.error('API Error:', err);
setError(err instanceof Error ? err.message : 'Errore durante l\'analisi. Verifica che il backend sia attivo.');
} finally {
setIsAnalyzing(false);
}
}; };
const handleCopyCommand = () => { const handleCopyCommand = () => {
@@ -119,6 +103,7 @@ export const InteractiveDemo: React.FC = () => {
}; };
const selectedLogData = DEMO_LOGS.find((l) => l.id === selectedLog); const selectedLogData = DEMO_LOGS.find((l) => l.id === selectedLog);
const SelectedIcon = selectedLogData?.icon || Terminal;
return ( return (
<section id="demo-interattiva" className="w-full py-24 lg:py-32 bg-white" aria-labelledby="demo-heading"> <section id="demo-interattiva" className="w-full py-24 lg:py-32 bg-white" aria-labelledby="demo-heading">
@@ -131,6 +116,9 @@ export const InteractiveDemo: React.FC = () => {
<p className="text-lg text-slate-600 max-w-2xl mx-auto"> <p className="text-lg text-slate-600 max-w-2xl mx-auto">
Seleziona un log di esempio e vedi come l'AI lo trasforma in un comando risolutivo in pochi secondi. Seleziona un log di esempio e vedi come l'AI lo trasforma in un comando risolutivo in pochi secondi.
</p> </p>
<p className="text-sm text-slate-500 mt-2">
Backend: <code className="bg-slate-100 px-2 py-1 rounded">{API_URL}</code>
</p>
</div> </div>
{/* Two Column Layout */} {/* Two Column Layout */}
@@ -154,22 +142,25 @@ export const InteractiveDemo: React.FC = () => {
<div className="p-4 border-b border-slate-700"> <div className="p-4 border-b border-slate-700">
<p className="text-slate-400 text-sm mb-3">Seleziona un log di esempio:</p> <p className="text-slate-400 text-sm mb-3">Seleziona un log di esempio:</p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{DEMO_LOGS.map((log) => ( {DEMO_LOGS.map((log) => {
<button const IconComponent = log.icon;
key={log.id} return (
onClick={() => handleLogSelect(log.id)} <button
disabled={isAnalyzing} key={log.id}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${ onClick={() => handleLogSelect(log.id)}
selectedLog === log.id disabled={isAnalyzing}
? 'bg-indigo-600 text-white' className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${
: 'bg-slate-700 text-slate-300 hover:bg-slate-600' selectedLog === log.id
} disabled:opacity-50 disabled:cursor-not-allowed`} ? 'bg-indigo-600 text-white'
aria-pressed={selectedLog === log.id} : 'bg-slate-700 text-slate-300 hover:bg-slate-600'
> } disabled:opacity-50 disabled:cursor-not-allowed`}
{log.icon} aria-pressed={selectedLog === log.id}
{log.label} >
</button> <IconComponent className="w-4 h-4" />
))} {log.label}
</button>
);
})}
</div> </div>
</div> </div>
@@ -197,7 +188,7 @@ export const InteractiveDemo: React.FC = () => {
aria-live="polite" aria-live="polite"
aria-atomic="true" aria-atomic="true"
> >
{!selectedLog && !isAnalyzing && !analysis && ( {!selectedLog && !isAnalyzing && !analysis && !error && (
<div className="h-full flex flex-col items-center justify-center text-center text-slate-400"> <div className="h-full flex flex-col items-center justify-center text-center text-slate-400">
<Activity className="w-16 h-16 mb-4 opacity-50" aria-hidden="true" /> <Activity className="w-16 h-16 mb-4 opacity-50" aria-hidden="true" />
<p className="text-lg">L'output dell'analisi apparirà qui</p> <p className="text-lg">L'output dell'analisi apparirà qui</p>
@@ -218,12 +209,24 @@ export const InteractiveDemo: React.FC = () => {
</div> </div>
)} )}
{analysis && !isAnalyzing && ( {error && !isAnalyzing && (
<div className="h-full flex flex-col items-center justify-center text-center">
<AlertCircle className="w-16 h-16 mb-4 text-red-500" aria-hidden="true" />
<p className="text-lg font-medium text-red-700 mb-2">Errore</p>
<p className="text-sm text-slate-600 mb-4">{error}</p>
<p className="text-xs text-slate-500">
Assicurati che il fake-backend sia in esecuzione:<br />
<code className="bg-slate-200 px-2 py-1 rounded">docker compose up fake-backend -d</code>
</p>
</div>
)}
{analysis && !isAnalyzing && !error && (
<div className="space-y-6"> <div className="space-y-6">
{/* Analysis Header */} {/* Analysis Header */}
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center flex-shrink-0"> <div className="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center flex-shrink-0">
<AlertCircle className="w-5 h-5 text-indigo-600" aria-hidden="true" /> <SelectedIcon className="w-5 h-5 text-indigo-600" aria-hidden="true" />
</div> </div>
<div> <div>
<h3 className="text-xl font-bold text-slate-900">{analysis.title}</h3> <h3 className="text-xl font-bold text-slate-900">{analysis.title}</h3>

View File

@@ -22,21 +22,56 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onComplete }
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const stepRef = useRef<HTMLDivElement>(null); const stepRef = useRef<HTMLDivElement>(null);
// Focus management for accessibility // Focus management for accessibility - only when step changes, not on initial mount
useEffect(() => { useEffect(() => {
if (stepRef.current) { // Only focus if this is a step change (not initial mount)
stepRef.current.focus(); // This prevents auto-scroll to the onboarding section on page load
if (stepRef.current && document.activeElement !== document.body) {
stepRef.current.focus({ preventScroll: true });
} }
}, [currentStep]); }, [currentStep]);
const generateWebhook = () => { // Environment configuration
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001';
const WEBHOOK_BASE_URL = import.meta.env.VITE_WEBHOOK_BASE_URL || `${API_URL}/webhook`;
const INSTALL_SCRIPT_URL = import.meta.env.VITE_INSTALL_SCRIPT_URL || `${API_URL}/install.sh`;
const APP_NAME = import.meta.env.VITE_APP_NAME || 'LogWhispererAI';
const generateWebhook = async () => {
setIsGenerating(true); setIsGenerating(true);
// Simulate generation delay
setTimeout(() => { try {
const uuid = crypto.randomUUID(); const response = await fetch(`${API_URL}/api/webhook`, {
setWebhookUrl(`https://logwhisperer.ai/webhook/${uuid}`); method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.success && data.uuid) {
// Use configured webhook base URL instead of API response
setWebhookUrl(`${WEBHOOK_BASE_URL}/${data.uuid}`);
} else {
throw new Error(data.message || 'Errore nella generazione del webhook');
}
} catch (error) {
console.error('Error generating webhook:', error);
// Fallback: generate locally if API fails
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);
});
setWebhookUrl(`${WEBHOOK_BASE_URL}/${uuid}`);
} finally {
setIsGenerating(false); setIsGenerating(false);
}, 1000); }
}; };
const copyWebhook = () => { const copyWebhook = () => {
@@ -46,7 +81,7 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onComplete }
}; };
const copyCurlCommand = () => { const copyCurlCommand = () => {
const command = `curl -fsSL https://logwhisperer.ai/install.sh | bash -s -- --webhook ${webhookUrl}`; const command = `curl -fsSL ${INSTALL_SCRIPT_URL} | bash -s -- --webhook ${webhookUrl}`;
navigator.clipboard.writeText(command); navigator.clipboard.writeText(command);
setCopied(true); setCopied(true);
setTimeout(() => setCopied(false), 2000); setTimeout(() => setCopied(false), 2000);
@@ -68,8 +103,8 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onComplete }
const getInstallCommand = () => { const getInstallCommand = () => {
return webhookUrl return webhookUrl
? `curl -fsSL https://logwhisperer.ai/install.sh | bash -s -- --webhook ${webhookUrl}` ? `curl -fsSL ${INSTALL_SCRIPT_URL} | bash -s -- --webhook ${webhookUrl}`
: 'curl -fsSL https://logwhisperer.ai/install.sh | bash'; : `curl -fsSL ${INSTALL_SCRIPT_URL} | bash`;
}; };
const steps = [ const steps = [
@@ -94,7 +129,7 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onComplete }
Inizia in 3 semplici passi Inizia in 3 semplici passi
</h2> </h2>
<p className="text-lg text-slate-600 max-w-2xl mx-auto"> <p className="text-lg text-slate-600 max-w-2xl mx-auto">
Configura LogWhisperer AI sul tuo server in meno di 5 minuti. Configura LogWhispererAI sul tuo server in meno di 5 minuti.
</p> </p>
</div> </div>
@@ -145,7 +180,7 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onComplete }
<Rocket className="w-10 h-10 text-indigo-600" aria-hidden="true" /> <Rocket className="w-10 h-10 text-indigo-600" aria-hidden="true" />
</div> </div>
<h3 className="text-2xl font-bold text-slate-900 mb-4"> <h3 className="text-2xl font-bold text-slate-900 mb-4">
Benvenuto su LogWhisperer AI Benvenuto su LogWhispererAI
</h3> </h3>
<p className="text-slate-600 mb-6 max-w-lg mx-auto"> <p className="text-slate-600 mb-6 max-w-lg mx-auto">
Il tuo assistente DevOps personale che monitora i log del server Il tuo assistente DevOps personale che monitora i log del server

View File

@@ -75,7 +75,7 @@ export const ProblemSolution: React.FC = () => {
🌌 🌌
</div> </div>
<div> <div>
<div className="font-semibold text-slate-900">LogWhisperer AI</div> <div className="font-semibold text-slate-900">LogWhispererAI</div>
<div className="text-xs text-slate-500">Ora</div> <div className="text-xs text-slate-500">Ora</div>
</div> </div>
</div> </div>

View File

@@ -10,5 +10,28 @@ export default defineConfig({
watch: { watch: {
usePolling: true, usePolling: true,
}, },
// Allow requests from reverse proxy host
// Add your production domain here
allowedHosts: [
'localhost',
'127.0.0.1',
'.lab.home.lucasacchi.net', // Allow all subdomains
'logwhispererai.lab.home.lucasacchi.net',
],
// HMR configuration - disabled when running behind reverse proxy
// HMR causes issues with WebSocket through HTTPS reverse proxy
hmr: process.env.NODE_ENV === 'production' ? false : {
// Use polling instead of WebSocket for HMR (more compatible with reverse proxy)
protocol: 'wss',
host: 'logwhispererai.lab.home.lucasacchi.net',
port: 443,
clientPort: 443,
},
// CORS configuration
cors: {
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
},
}, },
}) })

33
tools/fake-backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# Logs
logs
*.log
# Environment variables
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,31 @@
# Dockerfile for Fake Backend
# Development mock API server for LogWhispererAI
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy server code
COPY server.js ./
# Expose port 3000
EXPOSE 3000
# Set environment variables
ENV PORT=3000
ENV DELAY_MS=1500
ENV NODE_ENV=production
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
# Start server
CMD ["node", "server.js"]

854
tools/fake-backend/package-lock.json generated Normal file
View File

@@ -0,0 +1,854 @@
{
"name": "fake-backend",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fake-backend",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"cors": "^2.8.6",
"express": "^5.2.1"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cors": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.1",
"mime-types": "^3.0.2",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/serve-static": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
"integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
}
}

View File

@@ -0,0 +1,17 @@
{
"name": "fake-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"cors": "^2.8.6",
"express": "^5.2.1"
}
}

View File

@@ -0,0 +1,273 @@
/**
* 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

View File

@@ -1,4 +1,4 @@
# LogWhisperer AI - Workflow n8n # LogWhispererAI - Workflow n8n
Workflow per l'ingestion sicura dei log con validazione HMAC-SHA256. Workflow per l'ingestion sicura dei log con validazione HMAC-SHA256.

View File

@@ -131,7 +131,7 @@ cd /home/google/Sources/LucaSacchiNet/LogWhispererAI
# Output atteso: # Output atteso:
# ========================================== # ==========================================
# LogWhisperer AI - Workflow Test Suite # LogWhispererAI - Workflow Test Suite
# Target: http://192.168.254.12:5678 # Target: http://192.168.254.12:5678
# ========================================== # ==========================================
# #

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# #
# LogWhisperer AI - Workflow Test Script # LogWhispererAI - Workflow Test Script
# Verifica che il workflow n8n risponda correttamente # Verifica che il workflow n8n risponda correttamente
# #
@@ -205,7 +205,7 @@ EOF
# Main # Main
main() { main() {
echo "==========================================" echo "=========================================="
echo "LogWhisperer AI - Workflow Test Suite" echo "LogWhispererAI - Workflow Test Suite"
echo "Target: ${N8N_URL}" echo "Target: ${N8N_URL}"
echo "==========================================" echo "=========================================="
echo "" echo ""