- 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.
415 lines
23 KiB
JSON
415 lines
23 KiB
JSON
{
|
||
"name": "LogWhisperer_Ingest",
|
||
"nodes": [
|
||
{
|
||
"parameters": {},
|
||
"id": "trigger-node",
|
||
"name": "Webhook Trigger",
|
||
"type": "n8n-nodes-base.webhook",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
250,
|
||
300
|
||
],
|
||
"webhookId": "logwhisperer-ingest",
|
||
"path": "logwhisperer/ingest",
|
||
"responseMode": "responseNode",
|
||
"options": {}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "// HMAC Validation Node\n// Verifica la firma HMAC-SHA256 secondo il Metodo Sacchi: Safety First\n\nconst crypto = require('crypto');\n\n// Recupera headers\nconst signatureHeader = $headers['x-logwhisperer-signature'];\nconst timestampHeader = $headers['x-logwhisperer-timestamp'];\n\n// Recupera secret da variabile ambiente\nconst secret = process.env.LOGWHISPERER_SECRET;\n\nif (!secret) {\n throw new Error('LOGWHISPERER_SECRET not configured');\n}\n\nif (!signatureHeader || !timestampHeader) {\n return [{\n json: {\n valid: false,\n error: 'Missing authentication headers',\n statusCode: 401\n }\n }];\n}\n\n// Estrai timestamp e signature dal formato: timestamp:signature\nconst parts = signatureHeader.split(':');\nif (parts.length !== 2) {\n return [{\n json: {\n valid: false,\n error: 'Invalid signature format',\n statusCode: 401\n }\n }];\n}\n\nconst receivedTimestamp = parts[0];\nconst receivedSignature = parts[1];\n\n// Verifica timestamp (anti-replay: max 5 minuti di differenza)\nconst now = Math.floor(Date.now() / 1000);\nconst requestTime = parseInt(timestampHeader, 10);\nconst timeDiff = Math.abs(now - requestTime);\n\nif (timeDiff > 300) {\n return [{\n json: {\n valid: false,\n error: 'Request timestamp too old',\n statusCode: 401\n }\n }];\n}\n\n// Ottieni il payload raw\nconst payload = JSON.stringify($input.first().json);\n\n// Calcola HMAC atteso: HMAC-SHA256(timestamp:payload)\nconst expectedSignature = crypto\n .createHmac('sha256', secret)\n .update(`${timestampHeader}:${payload}`)\n .digest('hex');\n\n// Comparazione timing-safe\nconst isValid = crypto.timingSafeEqual(\n Buffer.from(receivedSignature, 'hex'),\n Buffer.from(expectedSignature, 'hex')\n);\n\nif (!isValid) {\n return [{\n json: {\n valid: false,\n error: 'Invalid signature',\n statusCode: 401\n }\n }];\n}\n\n// Validazione HMAC passata - restituisci payload per il prossimo nodo\nreturn [{\n json: {\n valid: true,\n data: $input.first().json\n }\n}];"
|
||
},
|
||
"id": "hmac-validation-node",
|
||
"name": "HMAC Validation",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
450,
|
||
300
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": true,
|
||
"leftValue": "",
|
||
"typeValidation": "strict"
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "condition-1",
|
||
"leftValue": "={{ $json.valid }}",
|
||
"rightValue": "true",
|
||
"operator": {
|
||
"type": "boolean",
|
||
"operation": "equals"
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
}
|
||
},
|
||
"id": "hmac-check-node",
|
||
"name": "HMAC Valid?",
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
650,
|
||
300
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "// Data Validation Node\n// Validazione campi obbligatori secondo il Metodo Sacchi: Double Check\n\nconst data = $input.first().json.data;\nconst errors = [];\n\n// Validazione UUID per client_id\nfunction isValidUUID(uuid) {\n const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;\n return uuidRegex.test(uuid);\n}\n\n// Validazione campi obbligatori\nif (!data.client_id) {\n errors.push('Missing client_id');\n} else if (!isValidUUID(data.client_id)) {\n errors.push('Invalid client_id format (must be UUID)');\n}\n\nif (!data.raw_log || data.raw_log.trim() === '') {\n errors.push('Missing or empty raw_log');\n}\n\nconst validSeverities = ['low', 'medium', 'critical'];\nif (!data.severity) {\n errors.push('Missing severity');\n} else if (!validSeverities.includes(data.severity.toLowerCase())) {\n errors.push(`Invalid severity: ${data.severity} (must be one of: ${validSeverities.join(', ')})`);\n}\n\nif (errors.length > 0) {\n return [{\n json: {\n valid: false,\n errors: errors,\n received: data\n }\n }];\n}\n\n// Normalizza severity a lowercase\nconst normalizedData = {\n ...data,\n severity: data.severity.toLowerCase()\n};\n\nreturn [{\n json: {\n valid: true,\n data: normalizedData\n }\n}];"
|
||
},
|
||
"id": "data-validation-node",
|
||
"name": "Data Validation",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
850,
|
||
200
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "executeQuery",
|
||
"query": "INSERT INTO logs (client_id, hostname, source, severity, timestamp, raw_log, matched_pattern)\nVALUES ($1, $2, $3, $4, $5, $6, $7)\nRETURNING id;",
|
||
"options": {
|
||
"queryParams": "={{ JSON.stringify([$json.data.client_id, $json.data.hostname, $json.data.source, $json.data.severity, $json.data.timestamp, $json.data.raw_log, $json.data.matched_pattern]) }}"
|
||
}
|
||
},
|
||
"id": "postgres-insert-node",
|
||
"name": "Store Log",
|
||
"type": "n8n-nodes-base.postgres",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
1050,
|
||
200
|
||
],
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "postgres-credentials",
|
||
"name": "PostgreSQL LogWhisperer"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "// ============================================================================\n// n8n Code Node: OpenRouter Processor\n// ============================================================================\n// Input: JSON dal nodo precedente (log data)\n// Output: Oggetto con analisi AI o fallback\n// Provider: OpenRouter (accesso a 300+ modelli AI)\n\nconst OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;\nconst OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';\nconst SITE_URL = process.env.OPENROUTER_SITE_URL || 'https://logwhisperer.ai';\nconst APP_NAME = process.env.OPENROUTER_APP_NAME || 'LogWhispererAI';\n\n// System Prompt completo con Metodo Sacchi\nconst SYSTEM_PROMPT = `Sei LogWhispererAI, un assistente DevOps esperto specializzato nell'analisi di log di sistema.\n\n## MISSIONE\nAnalizza i log ricevuti e fornisci insight azionabili in italiano, semplice e chiaro.\n\n## PRINCIPI OBBLIGATORI (Metodo Sacchi)\n\n### 1. SAFETY FIRST - Sicurezza Prima di Tutto\nMAI suggerire comandi che possano:\n- Cancellare dati (rm, del, truncate senza backup)\n- Modificare configurazioni critiche senza verifica\n- Riavviare servizi in produzione senza warning\n- Eseguire operazioni irreversibili\n\nREGOLE DI SICUREZZA:\n- Preferisci SEMPRE comandi read-only (cat, grep, df, ps, etc.)\n- Se un comando potrebbe essere distruttivo, imposta \\\"sicuro\\\": false\n- Per operazioni rischiose, richiedi sempre conferma umana\n- Non assumere mai che l'utente sappia cosa sta facendo\n\n### 2. LITTLE OFTEN - Piccoli Passi Verificabili\n- Suggerisci UN solo comando alla volta\n- Ogni azione deve essere verificabile prima della prossima\n- Dividi problemi complessi in step incrementali\n- Preferisci diagnostica prima della mitigazione\n\n### 3. DOUBLE CHECK - Verifica le Ipotesi\n- Non assumere MAI il contesto senza verificarlo\n- Se mancano informazioni, chiedi chiarimenti (richiede_conferma: true)\n- Verifica sempre i presupposti prima di suggerire azioni\n- Se l'errore e' ambiguo, richiedi analisi manuale\n\n## FORMATO OUTPUT OBBLIGATORIO\n\nRispondi SEMPRE in formato JSON valido con questa struttura:\n\n{\n \\\"sintesi\\\": \\\"Descrizione breve e chiara del problema in italiano\\\",\n \\\"severita\\\": \\\"low|medium|critical\\\",\n \\\"comando\\\": \\\"Comando bash esatto per diagnostica/mitigazione (o null)\\\",\n \\\"sicuro\\\": true|false,\n \\\"note\\\": \\\"Istruzioni aggiuntive, link documentazione, o 'Nessuna nota aggiuntiva'\\\",\n \\\"richiede_conferma\\\": true|false\n}\n\n## REGOLE JSON\n\n1. \\\"sintesi\\\": Massimo 150 caratteri, linguaggio semplice\n2. \\\"severita\\\": \n - \\\"critical\\\": Servizio down, rischio data loss, security breach\n - \\\"medium\\\": Degradazione performance, warning spazio disco\n - \\\"low\\\": Messaggi informativi, warning minori\n3. \\\"comando\\\": \n - Deve essere copiabile e incollabile in terminale\n - Se incerto o rischioso, usa null\n - Preferisci diagnostica (ls, grep, df) a modifica (rm, kill)\n4. \\\"sicuro\\\": \n - true solo se comando e' read-only o sicuro al 100%\n - false se potenzialmente distruttivo\n5. \\\"note\\\": \n - Spiega il perche' del comando\n - Aggiungi link a documentazione rilevante\n - Suggerisci verifiche aggiuntive\n6. \\\"richiede_conferma\\\": \n - true se serve conferma umana prima di eseguire\n - true se il problema e' ambiguo o poco chiaro\n\n## ESEMPI\n\nLog: \\\"OutOfMemoryError: Java heap space\\\"\n{\n \\\"sintesi\\\": \\\"Applicazione Java ha esaurito la memoria heap\\\",\n \\\"severita\\\": \\\"critical\\\",\n \\\"comando\\\": \\\"ps aux | grep java | head -5 && free -h\\\",\n \\\"sicuro\\\": true,\n \\\"note\\\": \\\"Verifica processi Java attivi e memoria disponibile. Se necessario, aumentare heap size in JVM options.\\\",\n \\\"richiede_conferma\\\": false\n}\n\nLog: \\\"Connection refused to database on port 5432\\\"\n{\n \\\"sintesi\\\": \\\"Impossibile connettersi al database PostgreSQL\\\",\n \\\"severita\\\": \\\"critical\\\",\n \\\"comando\\\": \\\"sudo systemctl status postgresql && netstat -tlnp | grep 5432\\\",\n \\\"sicuro\\\": true,\n \\\"note\\\": \\\"Verifica stato del servizio PostgreSQL. Se stopped: 'sudo systemctl start postgresql'\\\",\n \\\"richiede_conferma\\\": true\n}\n\nLog: \\\"Disk space warning: /var at 85%\\\"\n{\n \\\"sintesi\\\": \\\"Spazio su disco in esaurimento (85% utilizzato)\\\",\n \\\"severita\\\": \\\"medium\\\",\n \\\"comando\\\": \\\"df -h /var && du -sh /var/log/* | sort -hr | head -10\\\",\n \\\"sicuro\\\": true,\n \\\"note\\\": \\\"Identifica file di log piu' grandi. Per pulizia sicura: usa find per eliminare log vecchi.\\\",\n \\\"richiede_conferma\\\": false\n}\n\n## COMANDI PROIBITI (NON MAI SUGGERIRE)\n- rm -rf / o varianti\n- dd if=/dev/zero (sovrascrittura dischi)\n- mkfs, fdisk (formattazione)\n- kill -9 senza verifica processo\n- chmod 777 ricorsivo\n- Qualsiasi comando con pipe a sh/bash senza verifica\n\n## COMANDI PREFERITI (READ-ONLY)\n- Diagnostica: ps, top, htop, df, du, free, iostat, netstat, ss\n- Log: tail, head, grep, cat, less, journalctl\n- Rete: ping, curl, netstat, ss, lsof\n- Systemd: systemctl status, journalctl -u\n\nRICORDA: L'utente potrebbe essere non-tecnico. Spiega in italiano semplice.`;\n\n// Input dal nodo precedente\nconst inputData = $input.first().json;\n\n// Trunca raw_log se troppo lungo\nconst maxLogLength = 2000;\nconst truncatedLog = inputData.raw_log \n ? inputData.raw_log.substring(0, maxLogLength)\n : 'Nessun log fornito';\n\n// Prepara payload per OpenRouter\nconst payload = {\n model: \\\"openai/gpt-4o-mini\\\",\n messages: [\n {\n role: \\\"system\\\",\n content: SYSTEM_PROMPT\n },\n {\n role: \\\"user\\\",\n content: JSON.stringify({\n timestamp: inputData.timestamp,\n severity: inputData.severity,\n source: inputData.source,\n hostname: inputData.hostname,\n client_id: inputData.client_id,\n raw_log: truncatedLog\n })\n }\n ],\n temperature: 0.3,\n max_tokens: 500,\n response_format: { type: \\\"json_object\\\" },\n store: false\n};\n\n// Timeout configurazione\nconst TIMEOUT_MS = 10000;\n\nasync function callOpenRouter() {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);\n \n const response = await fetch(OPENROUTER_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + OPENROUTER_API_KEY,\n 'HTTP-Referer': SITE_URL,\n 'X-Title': APP_NAME\n },\n body: JSON.stringify(payload),\n signal: controller.signal\n });\n \n clearTimeout(timeoutId);\n \n if (!response.ok) {\n throw new Error('OpenRouter API error: ' + response.status + ' ' + response.statusText);\n }\n \n const data = await response.json();\n const aiResponse = JSON.parse(data.choices[0].message.content);\n \n // Aggiungi info modello usato (per tracking)\n const modelUsed = data.model || 'unknown';\n \n // Merge con dati originali\n return [{\n json: {\n client_id: inputData.client_id,\n hostname: inputData.hostname,\n severity: inputData.severity,\n raw_log: inputData.raw_log,\n ai_analysis: aiResponse,\n ai_status: 'success',\n ai_timestamp: new Date().toISOString(),\n ai_model: modelUsed\n }\n }];\n \n } catch (error) {\n console.error('OpenRouter Error:', error.message);\n \n // Fallback response\n return [{\n json: {\n client_id: inputData.client_id,\n hostname: inputData.hostname,\n severity: inputData.severity,\n raw_log: inputData.raw_log,\n ai_analysis: {\n sintesi: \\\"Errore durante analisi AI\\\",\n severita: inputData.severity || \\\"medium\\\",\n comando: null,\n sicuro: true,\n note: 'Fallback: ' + error.message + '. Controlla manualmente il log.',\n richiede_conferma: true\n },\n ai_status: 'fallback',\n ai_error: error.message,\n ai_timestamp: new Date().toISOString()\n }\n }];\n }\n}\n\n// Esegui chiamata\nreturn await callOpenRouter();"
|
||
},
|
||
"id": "openrouter-node",
|
||
"name": "Call OpenRouter",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
1250,
|
||
200
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": false,
|
||
"leftValue": "",
|
||
"typeValidation": "strict"
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "condition-1",
|
||
"leftValue": "={{ $json.severity }}",
|
||
"rightValue": "critical",
|
||
"operator": {
|
||
"type": "string",
|
||
"operation": "equals"
|
||
}
|
||
}
|
||
],
|
||
"combinator": "or"
|
||
}
|
||
},
|
||
"id": "severity-check-node",
|
||
"name": "Critical Severity?",
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
1450,
|
||
100
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"chatId": "={{ process.env.TELEGRAM_CHAT_ID }}",
|
||
"text": "={{ '🚨 *LogWhisperer Alert* \\n\\n📍 *Server:* ' + $json.hostname + '\\n⚠️ *Severity:* ' + $json.severity.toUpperCase() + '\\n\\n📝 *Problema:*\\n' + $json.ai_analysis.sintesi + '\\n\\n💡 *Comando suggerito:*\\n```bash\\n' + ($json.ai_analysis.comando || 'Nessun comando disponibile') + '\\n```\\n\\n⚠️ *Richiede conferma:* ' + ($json.ai_analysis.richiede_conferma ? 'SÌ' : 'NO') + '\\n📝 *Note:* ' + ($json.ai_analysis.note || 'Nessuna nota') + '\\n\\n📊 *Analisi AI:* ' + $json.ai_status + '\\n🤖 *Modello:* ' + ($json.ai_model || 'N/A') + '\\n\\n⏰ ' + new Date().toLocaleString('it-IT') }}",
|
||
"parseMode": "MarkdownV2",
|
||
"options": {
|
||
"disable_notification": false
|
||
}
|
||
},
|
||
"id": "telegram-notification-node",
|
||
"name": "Send Telegram Notification",
|
||
"type": "n8n-nodes-base.telegram",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
1650,
|
||
100
|
||
],
|
||
"continueOnFail": true,
|
||
"credentials": {
|
||
"telegramApi": {
|
||
"id": "telegram-credentials",
|
||
"name": "Telegram Bot"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "// AI Processing - Enhanced with OpenRouter Analysis\n// Riceve i dati elaborati da OpenRouter con analisi AI\n\nconst logData = $input.first().json;\n\n// Log di sicurezza: non esporre raw_log completo nei log\nconsole.log('AI Processing completed for log ID:', logData.client_id);\nconsole.log('Client:', logData.client_id);\nconsole.log('Severity:', logData.severity);\nconsole.log('AI Status:', logData.ai_status);\nconsole.log('AI Model:', logData.ai_model || 'N/A');\n\n// Se c'\u00e8 l'analisi AI, logga la sintesi\nif (logData.ai_analysis) {\n console.log('AI Sintesi:', logData.ai_analysis.sintesi);\n console.log('AI Comando suggerito:', logData.ai_analysis.comando || 'Nessuno');\n}\n\nreturn [{\n json: {\n status: 'ai_processing_complete',\n client_id: logData.client_id,\n hostname: logData.hostname,\n severity: logData.severity,\n ai_analysis: logData.ai_analysis,\n ai_status: logData.ai_status,\n ai_model: logData.ai_model,\n timestamp: logData.ai_timestamp || new Date().toISOString()\n }\n}];"
|
||
},
|
||
"id": "ai-processing-node",
|
||
"name": "AI Processing",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
1850,
|
||
100
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"respondWith": "json",
|
||
"options": {}
|
||
},
|
||
"id": "success-response-node",
|
||
"name": "Success Response",
|
||
"type": "n8n-nodes-base.respondToWebhook",
|
||
"typeVersion": 1.1,
|
||
"position": [
|
||
1650,
|
||
250
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"respondWith": "json",
|
||
"responseBody": "={\"error\": \"Unauthorized\", \"message\": \"Invalid HMAC signature\"}",
|
||
"options": {
|
||
"responseCode": 401
|
||
}
|
||
},
|
||
"id": "unauthorized-response-node",
|
||
"name": "401 Unauthorized",
|
||
"type": "n8n-nodes-base.respondToWebhook",
|
||
"typeVersion": 1.1,
|
||
"position": [
|
||
850,
|
||
400
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"respondWith": "json",
|
||
"responseBody": "={\"error\": \"Bad Request\", \"message\": $json.errors.join(\", \")}",
|
||
"options": {
|
||
"responseCode": 400
|
||
}
|
||
},
|
||
"id": "validation-error-node",
|
||
"name": "400 Validation Error",
|
||
"type": "n8n-nodes-base.respondToWebhook",
|
||
"typeVersion": 1.1,
|
||
"position": [
|
||
1050,
|
||
400
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "// Ensure Table Exists\n// Crea la tabella logs se non esiste gi\u00e0\n\nreturn [{\n json: {\n sql: `\n CREATE TABLE IF NOT EXISTS logs (\n id SERIAL PRIMARY KEY,\n client_id VARCHAR(36) NOT NULL,\n hostname VARCHAR(255),\n source VARCHAR(500),\n severity VARCHAR(20) CHECK (severity IN ('low', 'medium', 'critical')),\n timestamp TIMESTAMP WITH TIME ZONE,\n raw_log TEXT,\n matched_pattern VARCHAR(100),\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n );\n \n CREATE INDEX IF NOT EXISTS idx_logs_client_id ON logs(client_id);\n CREATE INDEX IF NOT EXISTS idx_logs_severity ON logs(severity);\n CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp);\n `\n }\n}];"
|
||
},
|
||
"id": "ensure-table-node",
|
||
"name": "Ensure Table SQL",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
650,
|
||
100
|
||
]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "executeQuery",
|
||
"query": "={{ $json.sql }}",
|
||
"options": {}
|
||
},
|
||
"id": "create-table-node",
|
||
"name": "Create Table",
|
||
"type": "n8n-nodes-base.postgres",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
850,
|
||
100
|
||
],
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "postgres-credentials",
|
||
"name": "PostgreSQL LogWhisperer"
|
||
}
|
||
}
|
||
}
|
||
],
|
||
"connections": {
|
||
"Webhook Trigger": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "HMAC Validation",
|
||
"type": "main",
|
||
"index": 0
|
||
},
|
||
{
|
||
"node": "Ensure Table SQL",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"HMAC Validation": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "HMAC Valid?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"HMAC Valid?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Data Validation",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "401 Unauthorized",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Data Validation": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Store Log",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "400 Validation Error",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Store Log": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Call OpenRouter",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Call OpenRouter": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Critical Severity?",
|
||
"type": "main",
|
||
"index": 0
|
||
},
|
||
{
|
||
"node": "Success Response",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Critical Severity?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Send Telegram Notification",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "Success Response",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Send Telegram Notification": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "AI Processing",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Ensure Table SQL": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Create Table",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
}
|
||
},
|
||
"settings": {
|
||
"executionOrder": "v1"
|
||
},
|
||
"staticData": null,
|
||
"tags": [
|
||
{
|
||
"name": "logwhisperer",
|
||
"id": "tag-logwhisperer",
|
||
"createdAt": "2026-04-02T00:00:00.000Z",
|
||
"updatedAt": "2026-04-02T00:00:00.000Z"
|
||
},
|
||
{
|
||
"name": "security",
|
||
"id": "tag-security",
|
||
"createdAt": "2026-04-02T00:00:00.000Z",
|
||
"updatedAt": "2026-04-02T00:00:00.000Z"
|
||
},
|
||
"ingestion"
|
||
]
|
||
} |