{"workflow":{"id":13565,"name":"Monitor Docker host health via SSH with GPT-4o-mini and alerts to Discord","views":62,"recentViews":0,"totalViews":62,"createdAt":"2026-02-20T23:02:03.626Z","description":"## This n8n template builds an automated health monitoring dashboard for your homelab Docker host.\n\n**It SSHs into your server, collects 30+ system and container metrics, analyzes trends with AI, and delivers a structured multi-embed dashboard to Discord -- plus real-time critical alerts when things go wrong.**\n\nStop SSHing into your server every morning to check if everything's still running. AI reads your metrics and tells you exactly what needs attention, with copy-paste fix commands.\n\n**Good to know**\n\n- Estimated cost is ~$0.003 per daily run using GPT-4o-mini. Compatible with Claude or any OpenAI-compatible LLM -- swap the model sub-node to switch providers. See the setup notes inside the workflow for Claude configuration.\n- Uses Google Sheets for 7-day metric history and trend analysis. A one-click setup trigger auto-creates a formatted tracking spreadsheet with frozen headers and conditional formatting.\n- The critical alert path runs every 5 minutes with a lightweight check (configurable). The daily digest runs once in the morning. Both schedules are adjustable.\n\n## How it works\n\n- Daily schedule trigger SSHs into your Docker host and collects system metrics (real CPU % from /proc/stat, memory, all filesystems, swap, network I/O, top processes, zombie processes, failed services) and Docker metrics (container status, CPU, memory, restarts, health checks, disk usage, dangling images) in ~2 seconds\n- A 100-point health score is calculated from weighted metrics across CPU, memory, disk, swap, containers, and system health\n- 7 days of historical data is loaded from Google Sheets for trend comparison\n- AI analyzes current vs. historical metrics and returns structured JSON with severity-tagged findings, CLI fix commands, trend analysis, and a top recommendation\n- A 4-embed Discord dashboard is delivered: status header with inline metrics, actionable findings, Docker ecosystem overview with trends, and a footer with timing and API cost\n- Today's metrics are stored in Google Sheets for future trend tracking\n- A separate lightweight path runs every 5 minutes checking critical thresholds (disk &gt; 90%, memory &gt; 95%, inodes &gt; 90%, containers down) and fires immediate alerts\n\n## How to use\n\n- Click \"Test workflow\" on the first-time setup trigger to auto-create your Google Sheets tracking dashboard\n- Copy the Sheet ID into the configuration node, add your Discord webhook URL, wire your SSH and OpenAI credentials, and activate\n- Full setup guides linked inside the workflow for SSH keys, API keys, and Discord webhooks\n\n## Requirements\n\n- SSH access to a Linux Docker host (key-based authentication) ([ssh key setup](https://www.nxsi.io/guides/ssh-key-setup)) \n- OpenAI API key or Anthropic API key ([OpenAI setup guide](https://nxsi.io/guides/openai-api-setup) | [Claude setup guide](https://nxsi.io/guides/claude-api-setup))\n- Google Sheets OAuth2 credential ([n8n docs](https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/))\n- Discord webhook URL ([setup guide](https://nxsi.io/guides/discord-webhook))\n\n## Customizing this workflow\n\n- Adjust alert thresholds in the configuration node (disk warning/critical, memory warning/critical, inode critical, restart threshold)\n- Change the daily digest and critical alert schedules in the trigger nodes\n- Swap OpenAI for Claude or Ollama by replacing the LLM sub-node\n- Replace Discord with Slack, Telegram, or ntfy by modifying the webhook payload format\n- Add additional SSH metrics by editing the collection commands","workflow":{"id":"fVm21DSKcT3JpgnI","meta":{"instanceId":"fcc09c267524c7883fb2ea9bebe8d5af97bd1018d108a487ee168526f71cc449","templateCredsSetupCompleted":true},"name":"Monitor Docker host health via SSH with AI analysis and alerts to Discord","tags":[],"nodes":[{"id":"sticky-overview","name":"Sticky Note - Overview","type":"n8n-nodes-base.stickyNote","position":[-560,-416],"parameters":{"width":620,"height":880,"content":"# Homelab Health Dashboard\n\nAutomated daily health monitoring for your homelab. SSH into your Docker host, collect 30+ system and container metrics, analyze with AI for actionable insights, and get a structured multi-embed dashboard delivered to Discord.\n\n### How it works\n1. Schedule Trigger fires daily at 7:00 AM (configurable)\n2. SSH collects system metrics (real CPU %, memory, all filesystems, swap, network I/O, top processes, zombies, failed services) and Docker metrics (container stats, health checks, disk usage, dangling images)\n3. A Code node parses everything into structured data and calculates a 100-point health score\n4. Historical metrics are loaded from Google Sheets for trend comparison\n5. AI analyzes current vs. historical data and returns structured JSON with severity-tagged findings and fix commands\n6. A 4-embed Discord dashboard is built: status header with inline metrics, actionable findings with CLI commands, Docker ecosystem + trends, and a footer with cost/timing\n7. Today's metrics are stored in Google Sheets for future trend analysis\n8. A separate path runs every 5 minutes for critical threshold alerts\n\n### Setup steps\n1. Add your SSH credentials - [SSH key setup guide](https://nxsi.io/guides/ssh-key-setup)\n2. Add your OpenAI API key - [OpenAI API setup guide](https://nxsi.io/guides/openai-api-setup)\n3. Add Google Sheets credentials - [n8n Google Sheets docs](https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/)\n4. Click \"Test workflow\" on the \"▶️ Run first-time setup\" trigger to create your tracking spreadsheet\n5. Copy the Sheet ID into the \"⚙️ Configure monitoring settings\" node\n6. Add your Discord webhook URL - [Discord webhook setup guide](https://nxsi.io/guides/discord-webhook)\n7. Wire credentials to the SSH, Google Sheets, and OpenAI nodes\n8. Run the daily digest path manually to verify, then activate\n\n### Customization\n- Change the digest schedule in the \"Run daily health check\" trigger node\n- Adjust thresholds in the configuration and alert settings nodes\n- Swap OpenAI for Claude or Ollama by replacing the LLM sub-node\n- Replace Discord with Telegram, Slack, or ntfy by changing the webhook URL and payload format"},"typeVersion":1},{"id":"sticky-config","name":"Sticky Note - Configuration","type":"n8n-nodes-base.stickyNote","position":[128,32],"parameters":{"color":7,"width":420,"height":440,"content":"## ⚙️ Configuration\nEdit the configuration node below to set your Discord webhook URL, alert thresholds, and monitoring preferences. All user settings are in one place."},"typeVersion":1},{"id":"sticky-collection","name":"Sticky Note - Data Collection","type":"n8n-nodes-base.stickyNote","position":[560,32],"parameters":{"color":7,"width":700,"height":440,"content":"## 📊 Data Collection\nTwo SSH commands collect 30+ metrics in ~2 seconds. System metrics (real CPU %, memory, all filesystems, swap, network I/O, top 5 processes, zombies, failed services, connections) and Docker stats (container status, CPU, memory, restarts, health checks, disk usage, dangling images, unused volumes)."},"typeVersion":1},{"id":"sticky-analysis","name":"Sticky Note - AI Analysis","type":"n8n-nodes-base.stickyNote","position":[1280,32],"parameters":{"color":7,"width":764,"height":440,"content":"## 🤖 AI Analysis\nLoads 7 days of historical metrics from Google Sheets, builds a context-rich prompt with all 30+ data points, and sends to the LLM. The AI returns structured JSON with severity-tagged findings, fix commands, trend analysis, and a top recommendation - no free-form text."},"typeVersion":1},{"id":"sticky-output","name":"Sticky Note - Delivery","type":"n8n-nodes-base.stickyNote","position":[2080,32],"parameters":{"color":7,"width":600,"height":520,"content":"## 📬 Delivery & Storage\nBuilds a 4-embed Discord dashboard: (1) status header with 6 inline metric fields, (2) severity-tagged AI findings with fix commands, (3) Docker ecosystem + network + trends + top recommendation, (4) footer with hostname, timestamp, duration, and API cost. Stores 16 metric columns in Google Sheets for trend analysis."},"typeVersion":1},{"id":"sticky-critical","name":"Sticky Note - Critical Alerts","type":"n8n-nodes-base.stickyNote","position":[128,592],"parameters":{"color":7,"width":1580,"height":400,"content":"## 🚨 Critical Alerts\nRuns every 5 minutes with a lightweight SSH check. Edit the \"⚙️ Alert settings\" node to configure your Discord webhook URL and alert thresholds. Fires immediately if disk > 90%, memory > 95%, inodes > 90%, CPU overloaded, or any container is down."},"typeVersion":1},{"id":"sticky-warning","name":"Sticky Note - API Cost Warning","type":"n8n-nodes-base.stickyNote","position":[1776,592],"parameters":{"color":3,"width":320,"height":192,"content":"⚠️ Requires OpenAI API key\n\n**Quick setup:** Create a key at [platform.openai.com/api-keys](https://platform.openai.com/api-keys), then add it in n8n via Credentials → OpenAI.\n\n**Full instructions:** [OpenAI API Setup for n8n](https://nxsi.io/guides/openai-api-setup) - covers account creation, model selection, and cost management."},"typeVersion":1},{"id":"daily-trigger","name":"Run daily health check","type":"n8n-nodes-base.scheduleTrigger","position":[208,240],"parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 7 * * *"}]}},"typeVersion":1.2},{"id":"config","name":"⚙️ Configure monitoring settings","type":"n8n-nodes-base.set","position":[400,240],"parameters":{"options":{},"assignments":{"assignments":[{"id":"cfg-webhook","name":"discord_webhook_url","type":"string","value":"https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN"},{"id":"cfg-disk-warn","name":"disk_warning_pct","type":"number","value":80},{"id":"cfg-disk-crit","name":"disk_critical_pct","type":"number","value":90},{"id":"cfg-mem-warn","name":"memory_warning_pct","type":"number","value":85},{"id":"cfg-mem-crit","name":"memory_critical_pct","type":"number","value":95},{"id":"cfg-inode-crit","name":"inode_critical_pct","type":"number","value":90},{"id":"cfg-restart","name":"container_restart_threshold","type":"number","value":3},{"id":"cfg-history","name":"history_days","type":"number","value":7},{"id":"cfg-sheet","name":"google_sheet_id","type":"string","value":"YOUR_GOOGLE_SHEET_ID"}]}},"typeVersion":3.4},{"id":"ssh-system","name":"Collect system metrics","type":"n8n-nodes-base.ssh","onError":"continueRegularOutput","position":[640,240],"parameters":{"command":"echo \"HOST=$(hostname)\" && echo \"UPTIME=$(uptime -s)\" && echo \"LOAD=$(cat /proc/loadavg)\" && echo \"MEM_TOTAL=$(free -m | awk '/Mem:/{print $2}')\" && echo \"MEM_USED=$(free -m | awk '/Mem:/{print $3}')\" && echo \"MEM_AVAIL=$(free -m | awk '/Mem:/{print $7}')\" && echo \"DISK_TOTAL=$(df -h / | tail -1 | awk '{print $2}')\" && echo \"DISK_USED=$(df -h / | tail -1 | awk '{print $3}')\" && echo \"DISK_PCT=$(df / | tail -1 | awk '{print $5}' | tr -d '%')\" && echo \"INODE_PCT=$(df -i / | tail -1 | awk '{print $5}' | tr -d '%')\" && echo \"SWAP_TOTAL=$(free -m | awk '/Swap:/{print $2}')\" && echo \"SWAP_USED=$(free -m | awk '/Swap:/{print $3}')\" && echo \"TEMP=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo N/A)\" && echo \"CORES=$(nproc)\" && CPU1=$(awk '/^cpu /{print $2+$3+$4+$5+$6+$7+$8\" \"$5}' /proc/stat) && sleep 1 && CPU2=$(awk '/^cpu /{print $2+$3+$4+$5+$6+$7+$8\" \"$5}' /proc/stat) && echo \"CPU_DELTA=$CPU1 $CPU2\" && echo \"---FS---\" && df -P -x tmpfs -x devtmpfs -x squashfs | tail -n +2 && echo \"---NET---\" && awk 'NR>2 && $1!~/(lo|docker|br-|veth)/{gsub(/:/, \"\", $1); print $1\"|\"$2\"|\"$10\"|\"$4\"|\"$12}' /proc/net/dev && echo \"---TOP5CPU---\" && ps aux --sort=-%cpu | awk 'NR>1 && NR<=6{print $11\"|\"$3\"|\"$4\"|\"$1}' && echo \"ZOMBIES=$(ps aux | awk '$8~/Z/{count++} END{print count+0}')\" && echo \"FAILED_SERVICES=$(systemctl --failed --no-legend 2>/dev/null | wc -l)\" && echo \"CONNECTIONS=$(ss -tun state established 2>/dev/null | tail -n +2 | wc -l)\"","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"sdMaZ6nyArVUurS5","name":"Projectbox SSH Key"}},"typeVersion":1},{"id":"ssh-docker","name":"Collect Docker container metrics","type":"n8n-nodes-base.ssh","onError":"continueRegularOutput","position":[880,240],"parameters":{"command":"echo \"RUNNING=$(docker ps -q | wc -l)\" && echo \"TOTAL=$(docker ps -aq | wc -l)\" && docker ps -a --format '{{.Names}}|{{.Status}}|{{.Image}}' && echo \"---STATS---\" && docker stats --no-stream --format '{{.Name}}|{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}|{{.PIDs}}' && echo \"---HEALTH---\" && docker inspect --format '{{.Name}}|{{.RestartCount}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' $(docker ps -q) 2>/dev/null && echo \"---DISKUSAGE---\" && docker system df --format '{{.Type}}|{{.TotalCount}}|{{.Size}}|{{.Reclaimable}}' && echo \"DANGLING=$(docker images -f dangling=true -q | wc -l)\" && echo \"UNUSED_VOLS=$(docker volume ls -f dangling=true -q | wc -l)\"","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"sdMaZ6nyArVUurS5","name":"Projectbox SSH Key"}},"typeVersion":1},{"id":"parse-metrics","name":"Parse and normalize all metrics","type":"n8n-nodes-base.code","position":[1120,240],"parameters":{"jsCode":"const sysOut = $('Collect system metrics').first().json.stdout || '';\nconst dkrOut = $('Collect Docker container metrics').first().json.stdout || '';\n\nconst m = {};\nfor (const line of sysOut.split('\\n')) {\n  const idx = line.indexOf('=');\n  if (idx > 0 && !line.startsWith('---') && !line.includes('|')) {\n    m[line.substring(0, idx).trim()] = line.substring(idx + 1).trim();\n  }\n}\n\n// CPU % from /proc/stat delta\nlet cpuPercent = 0;\nif (m.CPU_DELTA) {\n  const parts = m.CPU_DELTA.split(' ');\n  if (parts.length >= 4) {\n    const total1 = parseFloat(parts[0]) || 0;\n    const idle1 = parseFloat(parts[1]) || 0;\n    const total2 = parseFloat(parts[2]) || 0;\n    const idle2 = parseFloat(parts[3]) || 0;\n    const totalDiff = total2 - total1;\n    const idleDiff = idle2 - idle1;\n    cpuPercent = totalDiff > 0 ? Math.round(((totalDiff - idleDiff) / totalDiff) * 1000) / 10 : 0;\n  }\n}\n\nconst loadParts = (m.LOAD || '0 0 0').split(' ');\nconst memTotal = parseInt(m.MEM_TOTAL) || 0;\nconst memUsed = parseInt(m.MEM_USED) || 0;\nconst memPct = memTotal > 0 ? Math.round((memUsed / memTotal) * 100) : 0;\nconst diskPct = parseInt(m.DISK_PCT) || 0;\nconst inodePct = parseInt(m.INODE_PCT) || 0;\nconst swapTotal = parseInt(m.SWAP_TOTAL) || 0;\nconst swapUsed = parseInt(m.SWAP_USED) || 0;\nconst swapPct = swapTotal > 0 ? Math.round((swapUsed / swapTotal) * 100) : 0;\nconst cores = parseInt(m.CORES) || 1;\nconst zombieCount = parseInt(m.ZOMBIES) || 0;\nconst failedServices = parseInt(m.FAILED_SERVICES) || 0;\nconst connections = parseInt(m.CONNECTIONS) || 0;\nlet cpuTemp = 'N/A';\nif (m.TEMP && m.TEMP !== 'N/A') cpuTemp = (parseInt(m.TEMP) / 1000).toFixed(1);\n\nlet uptimeDays = 0;\nif (m.UPTIME) {\n  const sd = new Date(m.UPTIME);\n  if (!isNaN(sd)) uptimeDays = Math.floor((Date.now() - sd.getTime()) / 86400000);\n}\n\n// Parse filesystems\nconst filesystems = [];\nconst fsSectionMatch = sysOut.split('---FS---');\nif (fsSectionMatch.length > 1) {\n  const fsBlock = fsSectionMatch[1].split('---NET---')[0] || fsSectionMatch[1].split('---TOP5CPU---')[0] || fsSectionMatch[1];\n  for (const line of fsBlock.split('\\n')) {\n    const cols = line.trim().split(/\\s+/);\n    if (cols.length >= 5 && cols[4].match(/\\d+/)) {\n      filesystems.push({\n        device: cols[0],\n        size: cols[1],\n        used: cols[2],\n        mountpoint: cols[5] || '/',\n        percent: parseInt(cols[4].replace('%', '')) || 0\n      });\n    }\n  }\n}\n\n// Parse network I/O\nlet netRxBytes = 0, netTxBytes = 0, netRxErrors = 0, netTxErrors = 0;\nconst netSection = sysOut.split('---NET---');\nif (netSection.length > 1) {\n  const netBlock = netSection[1].split('---TOP5CPU---')[0] || netSection[1];\n  for (const line of netBlock.split('\\n')) {\n    if (!line.includes('|')) continue;\n    const parts = line.split('|');\n    if (parts.length >= 5) {\n      netRxBytes += parseInt(parts[1]) || 0;\n      netTxBytes += parseInt(parts[2]) || 0;\n      netRxErrors += parseInt(parts[3]) || 0;\n      netTxErrors += parseInt(parts[4]) || 0;\n    }\n  }\n}\n\n// Parse top 5 processes by CPU\nconst topProcessesCpu = [];\nconst topSection = sysOut.split('---TOP5CPU---');\nif (topSection.length > 1) {\n  const topBlock = topSection[1].split('ZOMBIES=')[0] || topSection[1];\n  for (const line of topBlock.split('\\n')) {\n    if (!line.includes('|')) continue;\n    const parts = line.split('|');\n    if (parts.length >= 3) {\n      topProcessesCpu.push({\n        command: (parts[0] || '').trim(),\n        cpu: parseFloat(parts[1]) || 0,\n        mem: parseFloat(parts[2]) || 0,\n        user: (parts[3] || '').trim()\n      });\n    }\n  }\n}\n\n// Docker parsing\nconst sections = dkrOut.split('---STATS---');\nconst containerLines = (sections[0] || '').split('\\n');\nconst statsAndHealth = (sections[1] || '').split('---HEALTH---');\nconst statsLines = (statsAndHealth[0] || '').split('\\n');\nconst healthAndDisk = (statsAndHealth[1] || '').split('---DISKUSAGE---');\nconst healthLines = (healthAndDisk[0] || '').split('\\n');\nconst diskUsageLines = (healthAndDisk[1] || '').split('\\n');\n\nlet running = 0, total = 0;\nconst containers = [];\nconst statsMap = {};\nconst healthMap = {};\n\nfor (const line of containerLines) {\n  if (line.startsWith('RUNNING=')) running = parseInt(line.split('=')[1]) || 0;\n  else if (line.startsWith('TOTAL=')) total = parseInt(line.split('=')[1]) || 0;\n  else if (line.includes('|')) {\n    const [name, status, image] = line.split('|');\n    if (name) containers.push({ name: name.trim(), status: (status || '').trim(), image: (image || '').trim() });\n  }\n}\n\nfor (const line of statsLines) {\n  if (!line.includes('|')) continue;\n  const [name, cpu, mem, memP, pids] = line.split('|');\n  if (name) statsMap[name.trim()] = { cpu: (cpu || '').trim(), mem: (mem || '').trim(), memPct: (memP || '').trim(), pids: (pids || '').trim() };\n}\n\nfor (const line of healthLines) {\n  if (!line.includes('|')) continue;\n  const parts = line.split('|');\n  let name = (parts[0] || '').trim();\n  if (name.startsWith('/')) name = name.substring(1);\n  if (name) healthMap[name] = { restarts: parseInt(parts[1]) || 0, health: (parts[2] || 'none').trim() };\n}\n\n// Docker disk usage\nconst dockerDisk = { images_size: 'N/A', images_reclaimable: 'N/A', containers_size: 'N/A', volumes_size: 'N/A', build_cache_size: 'N/A', dangling_images: 0, unused_volumes: 0 };\nfor (const line of diskUsageLines) {\n  if (line.startsWith('DANGLING=')) { dockerDisk.dangling_images = parseInt(line.split('=')[1]) || 0; continue; }\n  if (line.startsWith('UNUSED_VOLS=')) { dockerDisk.unused_volumes = parseInt(line.split('=')[1]) || 0; continue; }\n  if (!line.includes('|')) continue;\n  const parts = line.split('|');\n  const type = (parts[0] || '').trim();\n  if (type === 'Images') { dockerDisk.images_size = (parts[2] || '').trim(); dockerDisk.images_reclaimable = (parts[3] || '').trim(); }\n  else if (type === 'Containers') dockerDisk.containers_size = (parts[2] || '').trim();\n  else if (type === 'Local Volumes') dockerDisk.volumes_size = (parts[2] || '').trim();\n  else if (type === 'Build Cache') dockerDisk.build_cache_size = (parts[2] || '').trim();\n}\n\nconst containerSummary = containers.map(c => {\n  const s = statsMap[c.name] || {};\n  const h = healthMap[c.name] || {};\n  return { name: c.name, status: c.status, image: c.image, cpu: s.cpu || '0%', memory: s.mem || 'N/A', memoryPct: s.memPct || '0%', restarts: h.restarts || 0, healthCheck: h.health || 'none' };\n});\n\nconst downCount = containers.filter(c => !c.status.startsWith('Up')).length;\nconst totalRestarts = containerSummary.reduce((sum, c) => sum + c.restarts, 0);\n\n// Health score with expanded factors\nlet score = 100;\nif (diskPct > 90) score -= 30; else if (diskPct > 80) score -= 15;\nif (memPct > 95) score -= 25; else if (memPct > 85) score -= 10;\nif (cpuPercent > 80) score -= 20; else if (cpuPercent > 50) score -= 10;\nif (parseFloat(loadParts[0]) > cores * 1.5) score -= 10; else if (parseFloat(loadParts[0]) > cores) score -= 5;\nif (inodePct > 90) score -= 20; else if (inodePct > 80) score -= 10;\nif (swapPct > 80) score -= 10; else if (swapPct > 50) score -= 5;\nif (totalRestarts > 5) score -= 15; else if (totalRestarts > 0) score -= 5;\nif (downCount > 0) score -= (downCount * 10);\nif (zombieCount > 0) score -= 5;\nif (failedServices > 0) score -= 10;\nconst reclaimStr = dockerDisk.images_reclaimable || '';\nif (reclaimStr.includes('%)')) {\n  const reclaimPct = parseInt(reclaimStr.match(/(\\d+)%/)?.[1] || '0');\n  if (reclaimPct > 50) score -= 5;\n}\nscore = Math.max(0, score);\n\nreturn [{ json: {\n  timestamp: new Date().toISOString(),\n  hostname: m.HOST || 'unknown',\n  uptime_days: uptimeDays,\n  cpu_percent: cpuPercent,\n  cpu_load_1m: parseFloat(loadParts[0]) || 0,\n  cpu_load_5m: parseFloat(loadParts[1]) || 0,\n  cpu_load_15m: parseFloat(loadParts[2]) || 0,\n  cores: cores,\n  memory_total_mb: memTotal,\n  memory_used_mb: memUsed,\n  memory_percent: memPct,\n  disk_total: m.DISK_TOTAL || 'N/A',\n  disk_used: m.DISK_USED || 'N/A',\n  disk_percent: diskPct,\n  inode_percent: inodePct,\n  swap_total_mb: swapTotal,\n  swap_used_mb: swapUsed,\n  swap_percent: swapPct,\n  cpu_temp_c: cpuTemp,\n  filesystems: filesystems,\n  net_rx_bytes: netRxBytes,\n  net_tx_bytes: netTxBytes,\n  net_rx_errors: netRxErrors,\n  net_tx_errors: netTxErrors,\n  top_processes_cpu: topProcessesCpu,\n  zombie_count: zombieCount,\n  failed_services: failedServices,\n  connections_established: connections,\n  containers_running: running,\n  containers_total: total,\n  containers_down: downCount,\n  total_restarts: totalRestarts,\n  health_score: score,\n  containers: containerSummary,\n  docker_disk: dockerDisk\n}}];"},"typeVersion":2},{"id":"read-history","name":"Read metrics history (last 7 days)","type":"n8n-nodes-base.googleSheets","onError":"continueRegularOutput","position":[1360,240],"parameters":{"options":{},"sheetName":{"__rl":true,"mode":"name","value":"metrics"},"documentId":{"__rl":true,"mode":"id","value":"={{ $('⚙️ Configure monitoring settings').first().json.google_sheet_id }}"}},"credentials":{"googleSheetsOAuth2Api":{"id":"VVs44FLf4I4fL8Jd","name":"Google Sheets account"}},"typeVersion":4.5,"alwaysOutputData":true},{"id":"build-prompt","name":"Build analysis prompt with history","type":"n8n-nodes-base.code","position":[1600,240],"parameters":{"jsCode":"const metrics = $('Parse and normalize all metrics').first().json;\nconst config = $('⚙️ Configure monitoring settings').first().json;\n\nconst historyItems = $('Read metrics history (last 7 days)').all();\nlet historyText = 'No historical data available yet (first run).';\nif (historyItems.length > 0 && historyItems[0].json.date) {\n  const recent = historyItems.slice(-7);\n  historyText = 'HISTORICAL DATA (last ' + recent.length + ' days):\\n';\n  for (const item of recent) {\n    const h = item.json;\n    historyText += h.date + ': CPU=' + (h.cpu_percent || h.cpu_load || 'N/A') + '% Mem=' + (h.memory_percent || 'N/A') + '% Disk=' + (h.disk_percent || 'N/A') + '% Swap=' + (h.swap_percent || 'N/A') + '% Score=' + (h.health_score || 'N/A') + ' Containers=' + (h.containers_running || '?') + '/' + (h.containers_total || '?') + ' Net_RX=' + (h.net_rx_bytes || 'N/A') + ' Net_TX=' + (h.net_tx_bytes || 'N/A') + '\\n';\n  }\n}\n\nlet containerTable = '';\nfor (const c of (metrics.containers || [])) {\n  const icon = c.status.startsWith('Up') ? 'UP' : 'DOWN';\n  const restart = c.restarts > 0 ? ' (' + c.restarts + ' restarts)' : '';\n  containerTable += icon + ' ' + c.name + ': ' + c.cpu + ' CPU, ' + c.memory + restart + '\\n';\n}\n\nlet fsTable = '';\nfor (const fs of (metrics.filesystems || [])) {\n  fsTable += fs.mountpoint + ': ' + fs.used + '/' + fs.size + ' (' + fs.percent + '%) [' + fs.device + ']\\n';\n}\n\nlet topProcs = '';\nfor (const p of (metrics.top_processes_cpu || [])) {\n  topProcs += p.command + ': ' + p.cpu + '% CPU, ' + p.mem + '% MEM (user: ' + p.user + ')\\n';\n}\n\nconst dk = metrics.docker_disk || {};\n\nconst prompt = 'Analyze this homelab and respond with ONLY valid JSON (no markdown, no code fences).\\n\\n' +\n  'SYSTEM METRICS:\\n' +\n  '- Host: ' + metrics.hostname + ' | Uptime: ' + metrics.uptime_days + ' days | Cores: ' + metrics.cores + '\\n' +\n  '- CPU: ' + metrics.cpu_percent + '% utilization | Load: ' + metrics.cpu_load_1m + '/' + metrics.cpu_load_5m + '/' + metrics.cpu_load_15m + '\\n' +\n  '- Memory: ' + metrics.memory_used_mb + 'MB/' + metrics.memory_total_mb + 'MB (' + metrics.memory_percent + '%)\\n' +\n  '- Swap: ' + metrics.swap_used_mb + 'MB/' + metrics.swap_total_mb + 'MB (' + metrics.swap_percent + '%)\\n' +\n  '- Temp: ' + metrics.cpu_temp_c + 'C | Connections: ' + metrics.connections_established + ' | Zombies: ' + metrics.zombie_count + ' | Failed services: ' + metrics.failed_services + '\\n\\n' +\n  'FILESYSTEMS:\\n' + (fsTable || 'Root only: ' + metrics.disk_used + '/' + metrics.disk_total + ' (' + metrics.disk_percent + '%)\\n') + '\\n' +\n  'NETWORK I/O (total across physical interfaces):\\n' +\n  '- RX: ' + (metrics.net_rx_bytes / 1073741824).toFixed(2) + ' GB | TX: ' + (metrics.net_tx_bytes / 1073741824).toFixed(2) + ' GB\\n' +\n  '- Errors: RX=' + metrics.net_rx_errors + ' TX=' + metrics.net_tx_errors + '\\n\\n' +\n  'TOP 5 PROCESSES BY CPU:\\n' + (topProcs || 'N/A\\n') + '\\n' +\n  'DOCKER ECOSYSTEM:\\n' +\n  '- Containers: ' + metrics.containers_running + ' running / ' + metrics.containers_total + ' total (' + metrics.containers_down + ' down)\\n' +\n  '- Images: ' + dk.images_size + ' (reclaimable: ' + dk.images_reclaimable + ')\\n' +\n  '- Volumes: ' + dk.volumes_size + ' | Build cache: ' + dk.build_cache_size + '\\n' +\n  '- Dangling images: ' + dk.dangling_images + ' | Unused volumes: ' + dk.unused_volumes + '\\n\\n' +\n  'CONTAINERS:\\n' + containerTable + '\\n' +\n  historyText + '\\n\\n' +\n  'Health Score: ' + metrics.health_score + '/100\\n' +\n  'Thresholds: Disk warn=' + (config.disk_warning_pct || 80) + '% crit=' + (config.disk_critical_pct || 90) + '% | Mem warn=' + (config.memory_warning_pct || 85) + '% crit=' + (config.memory_critical_pct || 95) + '%\\n\\n' +\n  'Respond with this exact JSON structure:\\n' +\n  '{\"status\":\"healthy|warning|critical\",\"headline\":\"One-sentence summary\",\"findings\":[{\"severity\":\"high|medium|low\",\"title\":\"Short title\",\"detail\":\"Explanation\",\"command\":\"fix command or null\"}],\"trend_summary\":\"Historical comparison or null if no data\",\"top_recommendation\":\"Single most impactful action\"}';\n\nreturn [{ json: { prompt: prompt } }];"},"typeVersion":2},{"id":"llm-chain","name":"Generate daily health digest","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[1792,240],"parameters":{"text":"={{ $json.prompt }}","batching":{},"messages":{"messageValues":[{"message":"You are a senior DevOps engineer reviewing a homelab. Every finding must be ACTIONABLE -- include the exact command to fix it or investigate it. Do not restate numbers the admin can already see in the dashboard. Focus on: what changed since yesterday, what is about to break, and what to do about it.\n\nRules:\n- Only report findings that require action or awareness. \"Memory is at 77%\" is NOT a finding. \"Memory increased 12% in 3 days, projected to hit 95% by Friday\" IS a finding.\n- Every finding with severity high or medium MUST include a command field with a real CLI command.\n- If no issues exist, return an empty findings array -- do not invent problems.\n- The headline should be conversational and specific, not generic.\n- Respond with ONLY valid JSON. No markdown, no code fences, no explanation text."}]},"promptType":"define"},"typeVersion":1.7},{"id":"openai-model","name":"OpenAI GPT-4o-mini","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[1840,464],"parameters":{"model":{"__rl":true,"mode":"id","value":"gpt-4o-mini"},"options":{"temperature":0.3}},"credentials":{"openAiApi":{"id":"zB25B6JMAORZyq1G","name":"OpenAi account"}},"typeVersion":1.2},{"id":"format-digest","name":"Format digest for Discord","type":"n8n-nodes-base.code","position":[2112,240],"parameters":{"mode":"runOnceForEachItem","jsCode":"const rawText = $json.text || $json.output || '{}';\nconst metrics = $('Parse and normalize all metrics').first().json;\nconst score = metrics.health_score || 0;\nconst startTime = new Date($('Parse and normalize all metrics').first().json.timestamp);\n\n// Parse AI JSON response\nlet ai = {};\ntry {\n  let cleaned = rawText.trim();\n  if (cleaned.startsWith('```')) {\n    cleaned = cleaned.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '');\n  }\n  ai = JSON.parse(cleaned);\n} catch (e) {\n  ai = { status: 'warning', headline: 'AI response was not valid JSON', findings: [{ severity: 'medium', title: 'AI Parse Error', detail: rawText.substring(0, 300), command: null }], trend_summary: null, top_recommendation: 'Check AI model output format' };\n}\n\n// Token/cost tracking\nlet inputTokens = 0, outputTokens = 0;\nif ($json.tokenUsageEstimate) {\n  inputTokens = $json.tokenUsageEstimate.promptTokens || 0;\n  outputTokens = $json.tokenUsageEstimate.completionTokens || 0;\n}\nif (inputTokens === 0) {\n  const promptText = $('Build analysis prompt with history').first().json.prompt || '';\n  inputTokens = Math.ceil((promptText.length + 800) / 4);\n  outputTokens = Math.ceil(rawText.length / 4);\n}\nconst costUsd = (inputTokens * 0.15 + outputTokens * 0.60) / 1000000;\nconst costDisplay = '$' + costUsd.toFixed(4);\n\n// Colors\nconst green = 0x00cc66;\nconst yellow = 0xffaa00;\nconst red = 0xff3333;\nconst color = score >= 80 ? green : score >= 50 ? yellow : red;\nconst statusEmoji = score >= 80 ? '\\u2705' : score >= 50 ? '\\u26a0\\ufe0f' : '\\ud83d\\udea8';\n\n// Format bytes\nconst fmtBytes = (b) => {\n  if (b >= 1073741824) return (b / 1073741824).toFixed(1) + ' GB';\n  if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MB';\n  if (b >= 1024) return (b / 1024).toFixed(1) + ' KB';\n  return b + ' B';\n};\n\n// Embed 1: Status header with inline fields\nconst fields = [\n  { name: '\\ud83d\\udcca Health', value: score + '/100', inline: true },\n  { name: '\\ud83d\\udda5\\ufe0f CPU', value: metrics.cpu_percent + '%', inline: true },\n  { name: '\\ud83e\\udde0 Memory', value: metrics.memory_percent + '%', inline: true },\n  { name: '\\ud83d\\udcbe Disk /', value: metrics.disk_percent + '%', inline: true },\n  { name: '\\ud83d\\udd04 Swap', value: metrics.swap_percent + '%', inline: true },\n  { name: '\\ud83d\\udc33 Containers', value: metrics.containers_running + '/' + metrics.containers_total, inline: true }\n];\n\n// Add any filesystem over 75%\nfor (const fs of (metrics.filesystems || [])) {\n  if (fs.percent >= 75 && fs.mountpoint !== '/') {\n    fields.push({ name: '\\ud83d\\udcbe ' + fs.mountpoint, value: fs.percent + '%', inline: true });\n  }\n}\n\nconst embed1 = {\n  title: ('\\ud83c\\udfe0 Homelab Health - ' + statusEmoji + ' ' + (ai.headline || 'Analysis complete')).substring(0, 256),\n  color: color,\n  fields: fields.slice(0, 9)\n};\n\n// Embed 2: Findings\nlet findingsText = '';\nconst findings = ai.findings || [];\nif (findings.length === 0) {\n  findingsText = '\\u2705 All clear - no issues detected.';\n} else {\n  for (const f of findings.slice(0, 6)) {\n    const icon = f.severity === 'high' ? '\\ud83d\\udd34' : f.severity === 'medium' ? '\\ud83d\\udfe1' : '\\ud83d\\udfe2';\n    const tag = f.severity.toUpperCase();\n    findingsText += icon + ' **' + tag + ': ' + (f.title || 'Issue') + '**\\n';\n    if (f.detail) findingsText += f.detail.substring(0, 200) + '\\n';\n    if (f.command) findingsText += '```\\n' + f.command.substring(0, 120) + '\\n```\\n';\n  }\n}\n\nconst embed2 = {\n  title: '\\ud83d\\udd0d Findings',\n  description: findingsText.substring(0, 2000),\n  color: color\n};\n\n// Embed 3: Docker ecosystem + trends\nconst dk = metrics.docker_disk || {};\nlet embed3Desc = '**Docker Disk**\\n';\nembed3Desc += '\\ud83d\\uddbc Images: ' + (dk.images_size || 'N/A') + ' (reclaimable: ' + (dk.images_reclaimable || 'N/A') + ')\\n';\nembed3Desc += '\\ud83d\\udce6 Volumes: ' + (dk.volumes_size || 'N/A') + ' | Cache: ' + (dk.build_cache_size || 'N/A') + '\\n';\nif (dk.dangling_images > 0 || dk.unused_volumes > 0) {\n  embed3Desc += '\\u26a0\\ufe0f Dangling images: ' + dk.dangling_images + ' | Unused volumes: ' + dk.unused_volumes + '\\n';\n}\nembed3Desc += '\\n**Network I/O**\\n';\nembed3Desc += '\\ud83d\\udce5 RX: ' + fmtBytes(metrics.net_rx_bytes) + ' | \\ud83d\\udce4 TX: ' + fmtBytes(metrics.net_tx_bytes) + '\\n';\nif (metrics.net_rx_errors > 0 || metrics.net_tx_errors > 0) {\n  embed3Desc += '\\u26a0\\ufe0f Errors: RX=' + metrics.net_rx_errors + ' TX=' + metrics.net_tx_errors + '\\n';\n}\nif (ai.trend_summary) {\n  embed3Desc += '\\n**Trends**\\n' + ai.trend_summary.substring(0, 300) + '\\n';\n}\nif (ai.top_recommendation) {\n  embed3Desc += '\\n\\ud83d\\udca1 **Top Recommendation:** ' + ai.top_recommendation.substring(0, 200);\n}\n\nconst embed3 = {\n  title: '\\ud83d\\udc33 Docker & Trends',\n  description: embed3Desc.substring(0, 1500),\n  color: color\n};\n\n// Embed 4: Footer\nconst now = new Date();\nconst cstTime = now.toLocaleString('en-US', { timeZone: 'America/Chicago', hour: 'numeric', minute: '2-digit', hour12: true });\nconst durationMs = now.getTime() - startTime.getTime();\nconst durationSec = (durationMs / 1000).toFixed(1);\n\nconst embed4 = {\n  description: metrics.hostname + ' | ' + cstTime + ' CST | ' + durationSec + 's | API: ' + costDisplay,\n  color: color\n};\n\nconst payload = {\n  embeds: [embed1, embed2, embed3, embed4]\n};\n\nreturn { json: payload };"},"typeVersion":2},{"id":"send-digest","name":"Send daily digest to Discord","type":"n8n-nodes-base.httpRequest","maxTries":3,"position":[2320,192],"parameters":{"url":"={{ $('⚙️ Configure monitoring settings').first().json.discord_webhook_url }}","method":"POST","options":{"timeout":10000},"jsonBody":"={{ JSON.stringify($json) }}","sendBody":true,"specifyBody":"json"},"retryOnFail":true,"typeVersion":4.2,"waitBetweenTries":2000},{"id":"store-metrics","name":"Store today's metrics in history","type":"n8n-nodes-base.googleSheets","position":[2528,368],"parameters":{"columns":{"value":{},"schema":[],"mappingMode":"autoMapInputData","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"name","value":"metrics"},"documentId":{"__rl":true,"mode":"id","value":"={{ $('⚙️ Configure monitoring settings').first().json.google_sheet_id }}"}},"credentials":{"googleSheetsOAuth2Api":{"id":"VVs44FLf4I4fL8Jd","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"critical-trigger","name":"Check for critical issues","type":"n8n-nodes-base.scheduleTrigger","position":[208,784],"parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"*/5 * * * *"}]}},"typeVersion":1.2},{"id":"ssh-quick","name":"Quick system health check","type":"n8n-nodes-base.ssh","onError":"continueRegularOutput","position":[576,784],"parameters":{"command":"echo \"DISK_PCT=$(df / | tail -1 | awk '{print $5}' | tr -d '%')\" && echo \"MEM_PCT=$(free | awk '/Mem:/{printf \"%.0f\", $3/$2*100}')\" && echo \"LOAD=$(cat /proc/loadavg | awk '{print $1}')\" && echo \"CORES=$(nproc)\" && echo \"INODE_PCT=$(df -i / | tail -1 | awk '{print $5}' | tr -d '%')\" && docker ps -a --filter status=exited --filter status=restarting --format 'DOWN={{.Names}}' && docker inspect --format 'RESTART={{.Name}}|{{.RestartCount}}' $(docker ps -q) 2>/dev/null","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"sdMaZ6nyArVUurS5","name":"Projectbox SSH Key"}},"typeVersion":1},{"id":"check-thresholds","name":"Check against critical thresholds","type":"n8n-nodes-base.code","position":[816,784],"parameters":{"mode":"runOnceForEachItem","jsCode":"const config = $('⚙️ Alert settings').first().json;\nconst out = $json.stdout || '';\nconst lines = out.split('\\n');\nconst m = {};\nconst downContainers = [];\nconst highRestarts = [];\n\nfor (const line of lines) {\n  if (line.startsWith('DOWN=')) {\n    downContainers.push(line.replace('DOWN=', '').trim());\n  } else if (line.startsWith('RESTART=')) {\n    const val = line.replace('RESTART=', '');\n    const cleaned = val.startsWith('/') ? val.substring(1) : val;\n    const parts = cleaned.split('|');\n    const count = parseInt(parts[1]) || 0;\n    if (count >= 3) highRestarts.push({ name: parts[0], count: count });\n  } else {\n    const idx = line.indexOf('=');\n    if (idx > 0) m[line.substring(0, idx).trim()] = line.substring(idx + 1).trim();\n  }\n}\n\nconst diskPct = parseInt(m.DISK_PCT) || 0;\nconst memPct = parseInt(m.MEM_PCT) || 0;\nconst load = parseFloat(m.LOAD) || 0;\nconst cores = parseInt(m.CORES) || 1;\nconst inodePct = parseInt(m.INODE_PCT) || 0;\n\nconst DISK_CRIT = config.disk_critical_pct || 90;\nconst MEM_CRIT = config.memory_critical_pct || 95;\nconst INODE_CRIT = config.inode_critical_pct || 90;\nconst LOAD_MULT = config.load_multiplier || 1.5;\n\nconst alerts = [];\nif (diskPct >= DISK_CRIT) alerts.push('🔴 **DISK CRITICAL:** ' + diskPct + '% used (threshold: ' + DISK_CRIT + '%)');\nif (memPct >= MEM_CRIT) alerts.push('🔴 **MEMORY CRITICAL:** ' + memPct + '% used (threshold: ' + MEM_CRIT + '%)');\nif (inodePct >= INODE_CRIT) alerts.push('🔴 **INODE CRITICAL:** ' + inodePct + '% used');\nif (load > cores * LOAD_MULT) alerts.push('🟡 **HIGH CPU LOAD:** ' + load.toFixed(1) + ' (' + cores + ' cores, threshold: ' + (cores * LOAD_MULT).toFixed(1) + ')');\nfor (const c of downContainers) alerts.push('🔴 **CONTAINER DOWN:** ' + c);\nfor (const r of highRestarts) alerts.push('🟡 **RESTART LOOP:** ' + r.name + ' has restarted ' + r.count + ' times');\n\nreturn { json: { has_critical: alerts.length > 0, alert_count: alerts.length, alerts: alerts, alert_message: alerts.join('\\n\\n'), disk_pct: diskPct, mem_pct: memPct, load: load, containers_down: downContainers.length } };"},"typeVersion":2},{"id":"if-critical","name":"Any critical issues found?","type":"n8n-nodes-base.if","position":[1056,784],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"loose"},"combinator":"and","conditions":[{"id":"crit-check","operator":{"type":"boolean","operation":"equals"},"leftValue":"={{ $json.has_critical }}","rightValue":true}]}},"typeVersion":2.3},{"id":"format-alert","name":"Format critical alert message","type":"n8n-nodes-base.code","position":[1296,768],"parameters":{"mode":"runOnceForEachItem","jsCode":"const data = $json;\nconst payload = {\n  content: '🚨 **HOMELAB CRITICAL ALERT**',\n  embeds: [{\n    title: data.alert_count + ' Critical Issue(s) Detected',\n    description: data.alert_message.substring(0, 4000),\n    color: 0xff3333,\n    fields: [\n      { name: 'Disk', value: data.disk_pct + '%', inline: true },\n      { name: 'Memory', value: data.mem_pct + '%', inline: true },\n      { name: 'CPU Load', value: data.load.toFixed(1), inline: true }\n    ],\n    timestamp: new Date().toISOString()\n  }]\n};\nreturn { json: payload };"},"typeVersion":2},{"id":"send-alert","name":"Send critical alert","type":"n8n-nodes-base.httpRequest","maxTries":3,"position":[1536,768],"parameters":{"url":"={{ $('⚙️ Alert settings').first().json.discord_webhook_url }}","method":"POST","options":{"timeout":10000},"jsonBody":"={{ JSON.stringify($json) }}","sendBody":true,"specifyBody":"json"},"retryOnFail":true,"typeVersion":4.2,"waitBetweenTries":2000},{"id":"sticky-setup","name":"Sticky Note - First-Time Setup","type":"n8n-nodes-base.stickyNote","position":[128,1088],"parameters":{"color":7,"width":1120,"height":364,"content":"## 🚀 First-Time Setup\nClick \"Test workflow\" on the trigger below to auto-create a formatted \"Homelab Health Dashboard\" Google Sheet with color-coded metrics tracking, frozen headers, and conditional formatting. Copy the Sheet ID from the output into your Config node. Only needs to run once."},"typeVersion":1},{"id":"setup-trigger","name":"▶️ Run first-time setup","type":"n8n-nodes-base.manualTrigger","position":[208,1264],"parameters":{},"typeVersion":1},{"id":"alert-config","name":"⚙️ Alert settings","type":"n8n-nodes-base.set","position":[384,784],"parameters":{"options":{},"assignments":{"assignments":[{"id":"alert-webhook","name":"discord_webhook_url","type":"string","value":"https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN"},{"id":"alert-disk","name":"disk_critical_pct","type":"number","value":90},{"id":"alert-mem","name":"memory_critical_pct","type":"number","value":95},{"id":"alert-inode","name":"inode_critical_pct","type":"number","value":90},{"id":"alert-load","name":"load_multiplier","type":"number","value":1.5}]}},"typeVersion":3.4},{"id":"setup-create","name":"Create health dashboard spreadsheet","type":"n8n-nodes-base.googleSheets","onError":"continueRegularOutput","position":[448,1264],"parameters":{"title":"Homelab Health Dashboard","options":{},"resource":"spreadsheet","sheetsUi":{"sheetValues":[{"title":"metrics"}]}},"credentials":{"googleSheetsOAuth2Api":{"id":"VVs44FLf4I4fL8Jd","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"setup-layout","name":"Build dashboard layout","type":"n8n-nodes-base.code","position":[688,1264],"parameters":{"mode":"runOnceForEachItem","jsCode":"const input = $json;\nconst spreadsheetId = input.spreadsheetId;\nconst spreadsheetUrl = input.spreadsheetUrl;\n\nconst sheets = input.sheets || [];\nconst metricsSheet = sheets.find(s => s.properties && s.properties.title === 'metrics');\nconst sheetId = metricsSheet ? metricsSheet.properties.sheetId : 0;\n\nconst headers = [\n  'date', 'hostname', 'cpu_percent', 'cpu_load', 'memory_percent',\n  'disk_percent', 'inode_percent', 'swap_percent', 'health_score',\n  'containers_running', 'containers_total', 'net_rx_bytes', 'net_tx_bytes',\n  'docker_reclaimable_gb', 'zombie_count', 'connections'\n];\n\nconst headerCells = headers.map(h => ({\n  userEnteredValue: { stringValue: h },\n  userEnteredFormat: {\n    backgroundColor: { red: 0.15, green: 0.15, blue: 0.18 },\n    textFormat: {\n      bold: true,\n      fontSize: 10,\n      foregroundColor: { red: 0.90, green: 0.90, blue: 0.93 }\n    },\n    horizontalAlignment: 'CENTER',\n    borders: {\n      bottom: { style: 'SOLID', width: 2, color: { red: 0.20, green: 0.60, blue: 0.86 } }\n    }\n  }\n}));\n\nconst columnWidths = [100, 120, 90, 85, 115, 95, 95, 95, 100, 135, 120, 110, 110, 140, 100, 100];\n\nconst requests = [\n  {\n    updateCells: {\n      rows: [{ values: headerCells }],\n      start: { sheetId: sheetId, rowIndex: 0, columnIndex: 0 },\n      fields: 'userEnteredValue,userEnteredFormat'\n    }\n  },\n  {\n    updateSheetProperties: {\n      properties: { sheetId: sheetId, gridProperties: { frozenRowCount: 1 } },\n      fields: 'gridProperties.frozenRowCount'\n    }\n  },\n  {\n    updateSheetProperties: {\n      properties: { sheetId: sheetId, tabColorStyle: { rgbColor: { red: 0.20, green: 0.60, blue: 0.86 } } },\n      fields: 'tabColorStyle'\n    }\n  },\n  // Health score gradient (col 8)\n  {\n    addConditionalFormatRule: {\n      rule: {\n        ranges: [{ sheetId: sheetId, startRowIndex: 1, startColumnIndex: 8, endColumnIndex: 9 }],\n        gradientRule: {\n          minpoint: { color: { red: 0.92, green: 0.60, blue: 0.60 }, type: 'NUMBER', value: '0' },\n          midpoint: { color: { red: 1.0, green: 0.85, blue: 0.40 }, type: 'NUMBER', value: '65' },\n          maxpoint: { color: { red: 0.56, green: 0.87, blue: 0.56 }, type: 'NUMBER', value: '100' }\n        }\n      },\n      index: 0\n    }\n  },\n  // CPU % gradient (col 2)\n  {\n    addConditionalFormatRule: {\n      rule: {\n        ranges: [{ sheetId: sheetId, startRowIndex: 1, startColumnIndex: 2, endColumnIndex: 3 }],\n        gradientRule: {\n          minpoint: { color: { red: 0.56, green: 0.87, blue: 0.56 }, type: 'NUMBER', value: '0' },\n          midpoint: { color: { red: 1.0, green: 0.85, blue: 0.40 }, type: 'NUMBER', value: '50' },\n          maxpoint: { color: { red: 0.92, green: 0.60, blue: 0.60 }, type: 'NUMBER', value: '100' }\n        }\n      },\n      index: 1\n    }\n  },\n  // Memory % gradient (col 4)\n  {\n    addConditionalFormatRule: {\n      rule: {\n        ranges: [{ sheetId: sheetId, startRowIndex: 1, startColumnIndex: 4, endColumnIndex: 5 }],\n        gradientRule: {\n          minpoint: { color: { red: 0.56, green: 0.87, blue: 0.56 }, type: 'NUMBER', value: '0' },\n          midpoint: { color: { red: 1.0, green: 0.85, blue: 0.40 }, type: 'NUMBER', value: '80' },\n          maxpoint: { color: { red: 0.92, green: 0.60, blue: 0.60 }, type: 'NUMBER', value: '100' }\n        }\n      },\n      index: 2\n    }\n  },\n  // Disk % gradient (col 5)\n  {\n    addConditionalFormatRule: {\n      rule: {\n        ranges: [{ sheetId: sheetId, startRowIndex: 1, startColumnIndex: 5, endColumnIndex: 6 }],\n        gradientRule: {\n          minpoint: { color: { red: 0.56, green: 0.87, blue: 0.56 }, type: 'NUMBER', value: '0' },\n          midpoint: { color: { red: 1.0, green: 0.85, blue: 0.40 }, type: 'NUMBER', value: '75' },\n          maxpoint: { color: { red: 0.92, green: 0.60, blue: 0.60 }, type: 'NUMBER', value: '100' }\n        }\n      },\n      index: 3\n    }\n  },\n  // Inode % gradient (col 6)\n  {\n    addConditionalFormatRule: {\n      rule: {\n        ranges: [{ sheetId: sheetId, startRowIndex: 1, startColumnIndex: 6, endColumnIndex: 7 }],\n        gradientRule: {\n          minpoint: { color: { red: 0.56, green: 0.87, blue: 0.56 }, type: 'NUMBER', value: '0' },\n          midpoint: { color: { red: 1.0, green: 0.85, blue: 0.40 }, type: 'NUMBER', value: '80' },\n          maxpoint: { color: { red: 0.92, green: 0.60, blue: 0.60 }, type: 'NUMBER', value: '100' }\n        }\n      },\n      index: 4\n    }\n  },\n  // Swap % gradient (col 7)\n  {\n    addConditionalFormatRule: {\n      rule: {\n        ranges: [{ sheetId: sheetId, startRowIndex: 1, startColumnIndex: 7, endColumnIndex: 8 }],\n        gradientRule: {\n          minpoint: { color: { red: 0.56, green: 0.87, blue: 0.56 }, type: 'NUMBER', value: '0' },\n          midpoint: { color: { red: 1.0, green: 0.85, blue: 0.40 }, type: 'NUMBER', value: '50' },\n          maxpoint: { color: { red: 0.92, green: 0.60, blue: 0.60 }, type: 'NUMBER', value: '100' }\n        }\n      },\n      index: 5\n    }\n  }\n];\n\ncolumnWidths.forEach((width, i) => {\n  requests.push({\n    updateDimensionProperties: {\n      range: { sheetId: sheetId, dimension: 'COLUMNS', startIndex: i, endIndex: i + 1 },\n      properties: { pixelSize: width },\n      fields: 'pixelSize'\n    }\n  });\n});\n\nreturn {\n  json: {\n    spreadsheetId,\n    spreadsheetUrl,\n    batchUpdateBody: { requests },\n    sheet_id: spreadsheetId,\n    sheet_url: spreadsheetUrl\n  }\n};"},"typeVersion":2},{"id":"setup-format","name":"Format spreadsheet as dashboard","type":"n8n-nodes-base.httpRequest","maxTries":2,"position":[928,1264],"parameters":{"url":"={{ \"https://sheets.googleapis.com/v4/spreadsheets/\" + $json.spreadsheetId + \":batchUpdate\" }}","method":"POST","options":{},"jsonBody":"={{ JSON.stringify($json.batchUpdateBody) }}","sendBody":true,"specifyBody":"json","authentication":"predefinedCredentialType","nodeCredentialType":"googleSheetsOAuth2Api"},"credentials":{"googleSheetsOAuth2Api":{"id":"VVs44FLf4I4fL8Jd","name":"Google Sheets account"}},"retryOnFail":true,"typeVersion":4.2,"waitBetweenTries":1000},{"id":"prepare-row","name":"Prepare metrics row for storage","type":"n8n-nodes-base.code","position":[2320,368],"parameters":{"jsCode":"const metrics = $('Parse and normalize all metrics').first().json;\nconst dk = metrics.docker_disk || {};\n\n// Parse reclaimable to GB\nlet reclaimableGb = 0;\nconst reclaimStr = dk.images_reclaimable || '';\nif (reclaimStr.includes('GB')) {\n  reclaimableGb = parseFloat(reclaimStr) || 0;\n} else if (reclaimStr.includes('MB')) {\n  reclaimableGb = (parseFloat(reclaimStr) || 0) / 1024;\n}\n\nreturn [{\n  json: {\n    date: metrics.timestamp.substring(0, 10),\n    hostname: metrics.hostname,\n    cpu_percent: metrics.cpu_percent,\n    cpu_load: metrics.cpu_load_1m,\n    memory_percent: metrics.memory_percent,\n    disk_percent: metrics.disk_percent,\n    inode_percent: metrics.inode_percent,\n    swap_percent: metrics.swap_percent,\n    health_score: metrics.health_score,\n    containers_running: metrics.containers_running,\n    containers_total: metrics.containers_total,\n    net_rx_bytes: metrics.net_rx_bytes,\n    net_tx_bytes: metrics.net_tx_bytes,\n    docker_reclaimable_gb: Math.round(reclaimableGb * 100) / 100,\n    zombie_count: metrics.zombie_count,\n    connections: metrics.connections_established\n  }\n}];"},"typeVersion":2}],"active":false,"pinData":{},"settings":{"timezone":"America/Chicago","callerPolicy":"workflowsFromSameOwner","errorWorkflow":"","availableInMCP":false,"executionOrder":"v1"},"versionId":"e8908ee6-2a41-4078-a940-e54d372bc9ec","connections":{"OpenAI GPT-4o-mini":{"ai_languageModel":[[{"node":"Generate daily health digest","type":"ai_languageModel","index":0}]]},"⚙️ Alert settings":{"main":[[{"node":"Quick system health check","type":"main","index":0}]]},"Build dashboard layout":{"main":[[{"node":"Format spreadsheet as dashboard","type":"main","index":0}]]},"Collect system metrics":{"main":[[{"node":"Collect Docker container metrics","type":"main","index":0}]]},"Run daily health check":{"main":[[{"node":"⚙️ Configure monitoring settings","type":"main","index":0}]]},"Check for critical issues":{"main":[[{"node":"⚙️ Alert settings","type":"main","index":0}]]},"Format digest for Discord":{"main":[[{"node":"Send daily digest to Discord","type":"main","index":0},{"node":"Prepare metrics row for storage","type":"main","index":0}]]},"Quick system health check":{"main":[[{"node":"Check against critical thresholds","type":"main","index":0}]]},"Any critical issues found?":{"main":[[{"node":"Format critical alert message","type":"main","index":0}],[]]},"▶️ Run first-time setup":{"main":[[{"node":"Create health dashboard spreadsheet","type":"main","index":0}]]},"Generate daily health digest":{"main":[[{"node":"Format digest for Discord","type":"main","index":0}]]},"Format critical alert message":{"main":[[{"node":"Send critical alert","type":"main","index":0}]]},"Parse and normalize all metrics":{"main":[[{"node":"Read metrics history (last 7 days)","type":"main","index":0}]]},"Prepare metrics row for storage":{"main":[[{"node":"Store today's metrics in history","type":"main","index":0}]]},"Collect Docker container metrics":{"main":[[{"node":"Parse and normalize all metrics","type":"main","index":0}]]},"Check against critical thresholds":{"main":[[{"node":"Any critical issues found?","type":"main","index":0}]]},"Build analysis prompt with history":{"main":[[{"node":"Generate daily health digest","type":"main","index":0}]]},"Read metrics history (last 7 days)":{"main":[[{"node":"Build analysis prompt with history","type":"main","index":0}]]},"Create health dashboard spreadsheet":{"main":[[{"node":"Build dashboard layout","type":"main","index":0}]]},"⚙️ Configure monitoring settings":{"main":[[{"node":"Collect system metrics","type":"main","index":0}]]}}},"lastUpdatedBy":29,"workflowInfo":{"nodeCount":32,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.set":{"count":2},"n8n-nodes-base.ssh":{"count":3},"n8n-nodes-base.code":{"count":7},"n8n-nodes-base.stickyNote":{"count":8},"n8n-nodes-base.httpRequest":{"count":3},"n8n-nodes-base.googleSheets":{"count":3},"n8n-nodes-base.manualTrigger":{"count":1},"n8n-nodes-base.scheduleTrigger":{"count":2},"@n8n/n8n-nodes-langchain.chainLlm":{"count":1},"@n8n/n8n-nodes-langchain.lmChatOpenAi":{"count":1}}},"status":"published","readyToDemo":null,"user":{"name":"nXsi","username":"dyllank","bio":"","verified":true,"links":["https://www.nxsi.io"],"avatar":"https://gravatar.com/avatar/05bc1aa9e985cd6db6b0dd88a651831586b48d677475e826dde0ecde2a899325?r=pg&d=retro&size=200"},"nodes":[{"id":18,"icon":"file:googleSheets.svg","name":"n8n-nodes-base.googleSheets","codex":{"data":{"alias":["CSV","Sheet","Spreadsheet","GS"],"resources":{"generic":[{"url":"https://n8n.io/blog/love-at-first-sight-ricardos-n8n-journey/","icon":"❤️","label":"Love at first sight: Ricardo’s n8n journey"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/supercharging-your-conference-registration-process-with-n8n/","icon":"🎫","label":"Supercharging your conference registration process with n8n"},{"url":"https://n8n.io/blog/creating-triggers-for-n8n-workflows-using-polling/","icon":"⏲","label":"Creating triggers for n8n workflows using polling"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/migrating-community-metrics-to-orbit-using-n8n/","icon":"📈","label":"Migrating Community Metrics to Orbit using n8n"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/your-business-doesnt-need-you-to-operate/","icon":" 🖥️","label":"Hey founders! Your business doesn't need you to operate"},{"url":"https://n8n.io/blog/how-honest-burgers-use-automation-to-save-100k-per-year/","icon":"🍔","label":"How Honest Burgers Use Automation to Save $100k per year"},{"url":"https://n8n.io/blog/how-a-digital-strategist-uses-n8n-for-online-marketing/","icon":"💻","label":"How a digital strategist uses n8n for online marketing"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googlesheets/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/"}]},"categories":["Data & Storage","Productivity"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"input\",\"output\"]","defaults":{"name":"Google Sheets"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGZpbGw9IiMyOEI0NDYiIGQ9Ik0zNS42OSAxIDUyIDE3LjIyNXYzOS4wODdhMy42NyAzLjY3IDAgMCAxLTEuMDg0IDIuNjFBMy43IDMuNyAwIDAgMSA0OC4yOTMgNjBIMTIuNzA3YTMuNyAzLjcgMCAwIDEtMi42MjMtMS4wNzhBMy42NyAzLjY3IDAgMCAxIDkgNTYuMzEyVjQuNjg4YTMuNjcgMy42NyAwIDAgMSAxLjA4NC0yLjYxQTMuNyAzLjcgMCAwIDEgMTIuNzA3IDF6Ii8+PHBhdGggZmlsbD0iIzZBQ0U3QyIgZD0iTTM1LjY5IDEgNTIgMTcuMjI1SDM5LjM5N2MtMi4wNTQgMC0zLjcwNy0xLjgyOS0zLjcwNy0zLjg3MnoiLz48cGF0aCBmaWxsPSIjMjE5QjM4IiBkPSJNMzkuMjExIDE3LjIyNSA1MiAyMi40OHYtNS4yNTV6Ii8+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTIwLjEyIDMxLjk3NWMwLS44MTcuNjYyLTEuNDc1IDEuNDgzLTEuNDc1aDE3Ljc5NGMuODIxIDAgMS40ODIuNjU4IDEuNDgyIDEuNDc1djE1LjQ4N2MwIC44MTgtLjY2MSAxLjQ3NS0xLjQ4MiAxLjQ3NUgyMS42MDNhMS40NzYgMS40NzYgMCAwIDEtMS40ODItMS40NzRWMzEuOTc0em0yLjIyNSAxLjQ3NWg2LjY3MnYyLjIxMmgtNi42NzJ6bTAgNS4xNjJoNi42NzJ2Mi4yMTNoLTYuNjcyem0wIDUuMTYzaDYuNjcydjIuMjEyaC02LjY3MnptOS42MzgtMTAuMzI1aDYuNjcydjIuMjEyaC02LjY3MnptMCA1LjE2Mmg2LjY3MnYyLjIxM2gtNi42NzJ6bTAgNS4xNjNoNi42NzJ2Mi4yMTJoLTYuNjcyeiIvPjxwYXRoIGZpbGw9IiMyOEI0NDYiIGQ9Ik0zNC42OSAwIDUxIDE2LjIyNXYzOS4wODdhMy42NyAzLjY3IDAgMCAxLTEuMDg0IDIuNjFBMy43IDMuNyAwIDAgMSA0Ny4yOTMgNTlIMTEuNzA3YTMuNyAzLjcgMCAwIDEtMi42MjMtMS4wNzhBMy42NyAzLjY3IDAgMCAxIDggNTUuMzEyVjMuNjg4YTMuNjcgMy42NyAwIDAgMSAxLjA4NC0yLjYxQTMuNyAzLjcgMCAwIDEgMTEuNzA3IDB6Ii8+PHBhdGggZmlsbD0iIzZBQ0U3QyIgZD0iTTM0LjY5IDAgNTEgMTYuMjI1SDM4LjM5N2MtMi4wNTQgMC0zLjcwNy0xLjgyOS0zLjcwNy0zLjg3MnoiLz48cGF0aCBmaWxsPSIjMjE5QjM4IiBkPSJNMzguMjExIDE2LjIyNSA1MSAyMS40OHYtNS4yNTV6Ii8+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE5LjEyIDMwLjk3NWMwLS44MTcuNjYyLTEuNDc1IDEuNDgzLTEuNDc1aDE3Ljc5NGMuODIxIDAgMS40ODIuNjU4IDEuNDgyIDEuNDc1djE1LjQ4N2MwIC44MTgtLjY2MSAxLjQ3NS0xLjQ4MiAxLjQ3NUgyMC42MDNhMS40NzYgMS40NzYgMCAwIDEtMS40ODItMS40NzRWMzAuOTc0em0yLjIyNSAxLjQ3NWg2LjY3MnYyLjIxMmgtNi42NzJ6bTAgNS4xNjJoNi42NzJ2Mi4yMTNoLTYuNjcyem0wIDUuMTYzaDYuNjcydjIuMjEyaC02LjY3MnptOS42MzgtMTAuMzI1aDYuNjcydjIuMjEyaC02LjY3MnptMCA1LjE2Mmg2LjY3MnYyLjIxM2gtNi42NzJ6bTAgNS4xNjNoNi42NzJ2Mi4yMTJoLTYuNjcyeiIvPjwvZz48L3N2Zz4="},"displayName":"Google Sheets","typeVersion":5,"nodeCategories":[{"id":3,"name":"Data & Storage"},{"id":4,"name":"Productivity"}]},{"id":19,"icon":"file:httprequest.svg","name":"n8n-nodes-base.httpRequest","codex":{"data":{"alias":["API","Request","URL","Build","cURL"],"resources":{"generic":[{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/learn-how-to-automatically-cross-post-your-content-with-n8n/","icon":"✍️","label":"Learn how to automatically cross-post your content with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/","icon":"🛳","label":"Running n8n on ships: An interview with Maranics"},{"url":"https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/","icon":" 🪢","label":"What are APIs and how to use them with no code"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/world-poetry-day-workflow/","icon":"📜","label":"Celebrating World Poetry Day with a daily poem in Telegram"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automate-designs-with-bannerbear-and-n8n/","icon":"🎨","label":"Automate Designs with Bannerbear and n8n"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/how-to-use-the-http-request-node-the-swiss-army-knife-for-workflow-automation/","icon":"🧰","label":"How to use the HTTP Request Node - The Swiss Army Knife for Workflow Automation"},{"url":"https://n8n.io/blog/learn-how-to-use-webhooks-with-mattermost-slash-commands/","icon":"🦄","label":"Learn how to use webhooks with Mattermost slash commands"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/automations-for-activists/","icon":"✨","label":"How Common Knowledge use workflow automation for activism"},{"url":"https://n8n.io/blog/creating-scheduled-text-affirmations-with-n8n/","icon":"🤟","label":"Creating scheduled text affirmations with n8n"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"output\"]","defaults":{"name":"HTTP Request","color":"#0004F5"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00MCAyMEM0MCA4Ljk1MzE0IDMxLjA0NjkgMCAyMCAwQzguOTUzMTQgMCAwIDguOTUzMTQgMCAyMEMwIDMxLjA0NjkgOC45NTMxNCA0MCAyMCA0MEMzMS4wNDY5IDQwIDQwIDMxLjA0NjkgNDAgMjBaTTIwIDM2Ljk0NThDMTguODg1MiAzNi45NDU4IDE3LjEzNzggMzUuOTY3IDE1LjQ5OTggMzIuNjk4NUMxNC43OTY0IDMxLjI5MTggMTQuMTk2MSAyOS41NDMxIDEzLjc1MjYgMjcuNjg0N0gyNi4xODk4QzI1LjgwNDUgMjkuNTQwMyAyNS4yMDQ0IDMxLjI5MDEgMjQuNTAwMiAzMi42OTg1QzIyLjg2MjIgMzUuOTY3IDIxLjExNDggMzYuOTQ1OCAyMCAzNi45NDU4Wk0xMi45MDY0IDIwQzEyLjkwNjQgMjEuNjA5NyAxMy4wMDg3IDIzLjE2NCAxMy4yMDAzIDI0LjYzMDVIMjYuNzk5N0MyNi45OTEzIDIzLjE2NCAyNy4wOTM2IDIxLjYwOTcgMjcuMDkzNiAyMEMyNy4wOTM2IDE4LjM5MDMgMjYuOTkxMyAxNi44MzYgMjYuNzk5NyAxNS4zNjk1SDEzLjIwMDNDMTMuMDA4NyAxNi44MzYgMTIuOTA2NCAxOC4zOTAzIDEyLjkwNjQgMjBaTTIwIDMuMDU0MTlDMjEuMTE0OSAzLjA1NDE5IDIyLjg2MjIgNC4wMzA3OCAyNC41MDAxIDcuMzAwMzlDMjUuMjA2NiA4LjcxNDA4IDI1LjgwNzIgMTAuNDA2NyAyNi4xOTIgMTIuMzE1M0gxMy43NTAxQzE0LjE5MzMgMTAuNDA0NyAxNC43OTQyIDguNzEyNTQgMTUuNDk5OCA3LjMwMDY0QzE3LjEzNzcgNC4wMzA4MyAxOC44ODUxIDMuMDU0MTkgMjAgMy4wNTQxOVpNMzAuMTQ3OCAyMEMzMC4xNDc4IDE4LjQwOTkgMzAuMDU0MyAxNi44NjE3IDI5LjgyMjcgMTUuMzY5NUgzNi4zMDQyQzM2LjcyNTIgMTYuODQyIDM2Ljk0NTggMTguMzk2NCAzNi45NDU4IDIwQzM2Ljk0NTggMjEuNjAzNiAzNi43MjUyIDIzLjE1OCAzNi4zMDQyIDI0LjYzMDVIMjkuODIyN0MzMC4wNTQzIDIzLjEzODMgMzAuMTQ3OCAyMS41OTAxIDMwLjE0NzggMjBaTTI2LjI3NjcgNC4yNTUxMkMyNy42MzY1IDYuMzYwMTkgMjguNzExIDkuMTMyIDI5LjM3NzQgMTIuMzE1M0gzNS4xMDQ2QzMzLjI1MTEgOC42NjggMzAuMTA3IDUuNzgzNDYgMjYuMjc2NyA0LjI1NTEyWk0xMC42MjI2IDEyLjMxNTNINC44OTI5M0M2Ljc1MTQ3IDguNjY3ODQgOS44OTM1MSA1Ljc4MzQxIDEzLjcyMzIgNC4yNTUxM0MxMi4zNjM1IDYuMzYwMjEgMTEuMjg5IDkuMTMyMDEgMTAuNjIyNiAxMi4zMTUzWk0zLjA1NDE5IDIwQzMuMDU0MTkgMjEuNjAzIDMuMjc3NDMgMjMuMTU3NSAzLjY5NDg0IDI0LjYzMDVIMTAuMTIxN0M5Ljk0NjE5IDIzLjE0MiA5Ljg1MjIyIDIxLjU5NDMgOS44NTIyMiAyMEM5Ljg1MjIyIDE4LjQwNTcgOS45NDYxOSAxNi44NTggMTAuMTIxNyAxNS4zNjk1SDMuNjk0ODRDMy4yNzc0MyAxNi44NDI1IDMuMDU0MTkgMTguMzk3IDMuMDU0MTkgMjBaTTI2LjI3NjYgMzUuNzQyN0MyNy42MzY1IDMzLjYzOTMgMjguNzExIDMwLjg2OCAyOS4zNzc0IDI3LjY4NDdIMzUuMTA0NkMzMy4yNTEgMzEuMzMyMiAzMC4xMDY4IDM0LjIxNzkgMjYuMjc2NiAzNS43NDI3Wk0xMy43MjM0IDM1Ljc0MjdDOS44OTM2OSAzNC4yMTc5IDYuNzUxNTUgMzEuMzMyNCA0Ljg5MjkzIDI3LjY4NDdIMTAuNjIyNkMxMS4yODkgMzAuODY4IDEyLjM2MzUgMzMuNjM5MyAxMy43MjM0IDM1Ljc0MjdaIiBmaWxsPSIjM0E0MkU5Ii8+Cjwvc3ZnPgo="},"displayName":"HTTP Request","typeVersion":4,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","codex":{"data":{"alias":["Router","Filter","Condition","Logic","Boolean","Branch"],"details":"The IF node can be used to implement binary conditional logic in your workflow. You can set up one-to-many conditions to evaluate each item of data being inputted into the node. That data will either evaluate to TRUE or FALSE and route out of the node accordingly.\n\nThis node has multiple types of conditions: Bool, String, Number, and Date & Time.","resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/create-a-toxic-language-detector-for-telegram/","icon":"🤬","label":"Create a toxic language detector for Telegram in 4 step"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"transform\"]","defaults":{"name":"If","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"displayName":"If","typeVersion":2,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":38,"icon":"fa:pen","name":"n8n-nodes-base.set","codex":{"data":{"alias":["Set","JS","JSON","Filter","Transform","Map"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/database-monitoring-and-alerting-with-n8n/","icon":"📡","label":"Database Monitoring and Alerting with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Data Transformation"]}}},"group":"[\"input\"]","defaults":{"name":"Edit Fields"},"iconData":{"icon":"pen","type":"icon"},"displayName":"Edit Fields (Set)","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":490,"icon":"fa:terminal","name":"n8n-nodes-base.ssh","codex":{"data":{"alias":["remote"],"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.ssh/"}]},"categories":["Core Nodes","Development"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":["Helpers"]}},"group":"[\"input\"]","defaults":{"name":"SSH","color":"#000000"},"iconData":{"icon":"terminal","type":"icon"},"displayName":"SSH","typeVersion":1,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":834,"icon":"file:code.svg","name":"n8n-nodes-base.code","codex":{"data":{"alias":["cpde","Javascript","JS","Python","Script","Custom Code","Function"],"details":"The Code node allows you to execute JavaScript in your workflow.","resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Code"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTcxXzQ0MSkiPgo8cGF0aCBkPSJNMTcwLjI4MyA0OEgxOTYuNUMyMDMuMTI3IDQ4IDIwOC41IDQyLjYyNzQgMjA4LjUgMzZWMTJDMjA4LjUgNS4zNzI1OCAyMDMuMTI3IDAgMTk2LjUgMEgxNzAuMjgzQzEyNi4xIDAgOTAuMjgzIDM1LjgxNzIgOTAuMjgzIDgwVjE3NkM5MC4yODMgMjA2LjkyOCA2NS4yMTA5IDIzMiAzNC4yODMgMjMySDIzQzE2LjM3MjYgMjMyIDExIDIzNy4zNzIgMTEgMjQ0VjI2OEMxMSAyNzQuNjI3IDE2LjM3MjQgMjgwIDIyLjk5OTYgMjgwTDM0LjI4MyAyODBDNjUuMjEwOSAyODAgOTAuMjgzIDMwNS4wNzIgOTAuMjgzIDMzNlY0NDBDOTAuMjgzIDQ3OS43NjQgMTIyLjUxOCA1MTIgMTYyLjI4MyA1MTJIMTk2LjVDMjAzLjEyNyA1MTIgMjA4LjUgNTA2LjYyNyAyMDguNSA1MDBWNDc2QzIwOC41IDQ2OS4zNzMgMjAzLjEyNyA0NjQgMTk2LjUgNDY0SDE2Mi4yODNDMTQ5LjAyOCA0NjQgMTM4LjI4MyA0NTMuMjU1IDEzOC4yODMgNDQwVjMzNkMxMzguMjgzIDMwOS4wMjIgMTI4LjAxMSAyODQuNDQzIDExMS4xNjQgMjY1Ljk2MUMxMDYuMTA5IDI2MC40MTYgMTA2LjEwOSAyNTEuNTg0IDExMS4xNjQgMjQ2LjAzOUMxMjguMDExIDIyNy41NTcgMTM4LjI4MyAyMDIuOTc4IDEzOC4yODMgMTc2VjgwQzEzOC4yODMgNjIuMzI2OSAxNTIuNjEgNDggMTcwLjI4MyA0OFoiIGZpbGw9IiNGRjk5MjIiLz4KPHBhdGggZD0iTTMwNSAzNkMzMDUgNDIuNjI3NCAzMTAuMzczIDQ4IDMxNyA0OEgzNDIuOTc5QzM2MC42NTIgNDggMzc0Ljk3OCA2Mi4zMjY5IDM3NC45NzggODBWMTc2QzM3NC45NzggMjAyLjk3OCAzODUuMjUxIDIyNy41NTcgNDAyLjA5OCAyNDYuMDM5QzQwNy4xNTMgMjUxLjU4NCA0MDcuMTUzIDI2MC40MTYgNDAyLjA5OCAyNjUuOTYxQzM4NS4yNTEgMjg0LjQ0MyAzNzQuOTc4IDMwOS4wMjIgMzc0Ljk3OCAzMzZWNDMyQzM3NC45NzggNDQ5LjY3MyAzNjAuNjUyIDQ2NCAzNDIuOTc5IDQ2NEgzMTdDMzEwLjM3MyA0NjQgMzA1IDQ2OS4zNzMgMzA1IDQ3NlY1MDBDMzA1IDUwNi42MjcgMzEwLjM3MyA1MTIgMzE3IDUxMkgzNDIuOTc5QzM4Ny4xNjEgNTEyIDQyMi45NzggNDc2LjE4MyA0MjIuOTc4IDQzMlYzMzZDNDIyLjk3OCAzMDUuMDcyIDQ0OC4wNTEgMjgwIDQ3OC45NzkgMjgwSDQ5MEM0OTYuNjI3IDI4MCA1MDIgMjc0LjYyOCA1MDIgMjY4VjI0NEM1MDIgMjM3LjM3MyA0OTYuNjI4IDIzMiA0OTAgMjMyTDQ3OC45NzkgMjMyQzQ0OC4wNTEgMjMyIDQyMi45NzggMjA2LjkyOCA0MjIuOTc4IDE3NlY4MEM0MjIuOTc4IDM1LjgxNzIgMzg3LjE2MSAwIDM0Mi45NzkgMEgzMTdDMzEwLjM3MyAwIDMwNSA1LjM3MjU4IDMwNSAxMlYzNloiIGZpbGw9IiNGRjk5MjIiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTcxXzQ0MSI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Code","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":838,"icon":"fa:mouse-pointer","name":"n8n-nodes-base.manualTrigger","codex":{"data":{"resources":{"generic":[],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.manualworkflowtrigger/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"trigger\"]","defaults":{"name":"When clicking ‘Execute workflow’","color":"#909298"},"iconData":{"icon":"mouse-pointer","type":"icon"},"displayName":"Manual Trigger","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":839,"icon":"fa:clock","name":"n8n-nodes-base.scheduleTrigger","codex":{"data":{"alias":["Time","Scheduler","Polling","Cron","Interval"],"resources":{"generic":[],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.scheduletrigger/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"trigger\",\"schedule\"]","defaults":{"name":"Schedule Trigger","color":"#31C49F"},"iconData":{"icon":"clock","type":"icon"},"displayName":"Schedule Trigger","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":1123,"icon":"fa:link","name":"@n8n/n8n-nodes-langchain.chainLlm","codex":{"data":{"alias":["LangChain"],"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Chains","Root Nodes"]}}},"group":"[\"transform\"]","defaults":{"name":"Basic LLM Chain","color":"#909298"},"iconData":{"icon":"link","type":"icon"},"displayName":"Basic LLM Chain","typeVersion":2,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]},{"id":1153,"icon":"file:openAiLight.svg","name":"@n8n/n8n-nodes-langchain.lmChatOpenAi","codex":{"data":{"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.lmchatopenai/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Language Models","Root Nodes"],"Language Models":["Chat Models (Recommended)"]}}},"group":"[\"transform\"]","defaults":{"name":"OpenAI Chat Model"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTM2Ljg2NzEgMTYuMzcxOEMzNy43NzQ2IDEzLjY0OCAzNy40NjIxIDEwLjY2NDIgMzYuMDEwOCA4LjE4NjYxQzMzLjgyODIgNC4zODY1MyAyOS40NDA3IDIuNDMxNDkgMjUuMTU1NiAzLjM1MTUxQzIzLjI0OTMgMS4yMDM5NiAyMC41MTA1IC0wLjAxNzMxNDggMTcuNjM5MiAwLjAwMDE4NTUzM0MxMy4yNTkxIC0wLjAwOTgxNDY4IDkuMzcyNzMgMi44MTAyNSA4LjAyNTIgNi45Nzc4M0M1LjIxMTM5IDcuNTU0MSAyLjc4MjU4IDkuMzE1MzggMS4zNjEzIDExLjgxMTdDLTAuODM3NDkzIDE1LjYwMTggLTAuMzM2MjMyIDIwLjM3OTQgMi42MDEzMyAyMy42Mjk0QzEuNjkzODEgMjYuMzUzMiAyLjAwNjMyIDI5LjMzNzEgMy40NTc2IDMxLjgxNDZDNS42NDAxNSAzNS42MTQ3IDEwLjAyNzcgMzcuNTY5NyAxNC4zMTI4IDM2LjY0OTdDMTYuMjE3OSAzOC43OTczIDE4Ljk1NzkgNDAuMDE4NSAyMS44MjkyIDM5Ljk5OThDMjYuMjExOCA0MC4wMTEgMzAuMDk5NCAzNy4xODg1IDMxLjQ0NjkgMzMuMDE3MUMzNC4yNjA4IDMyLjQ0MDkgMzYuNjg5NiAzMC42Nzk2IDM4LjExMDggMjguMTgzM0M0MC4zMDcxIDI0LjM5MzIgMzkuODA0NiAxOS42MTk0IDM2Ljg2ODMgMTYuMzY5M0wzNi44NjcxIDE2LjM3MThaTTIxLjgzMTcgMzcuMzg2QzIwLjA3OCAzNy4zODg1IDE4LjM3OTIgMzYuNzc0NyAxNy4wMzI5IDM1LjY1MDlDMTcuMDk0MSAzNS42MTg0IDE3LjIwMDQgMzUuNTU5NyAxNy4yNjkxIDM1LjUxNzJMMjUuMjM0MyAzMC45MTcxQzI1LjY0MTggMzAuNjg1OCAyNS44OTE4IDMwLjI1MjEgMjUuODg5MyAyOS43ODMzVjE4LjU1NDNMMjkuMjU1NyAyMC40OTgxQzI5LjI5MTkgMjAuNTE1NiAyOS4zMTU3IDIwLjU1MDYgMjkuMzIwNyAyMC41OTA2VjI5Ljg4OTZDMjkuMzE1NyAzNC4wMjQ3IDI1Ljk2NjggMzcuMzc3MiAyMS44MzE3IDM3LjM4NlpNNS43MjY0IDMwLjUwNzFDNC44NDc2MyAyOC45ODk2IDQuNTMxMzcgMjcuMjEwOCA0LjgzMjYzIDI1LjQ4NDVDNC44OTEzOCAyNS41MTk1IDQuOTk1MTMgMjUuNTgzMiA1LjA2ODg4IDI1LjYyNTdMMTMuMDM0MSAzMC4yMjU4QzEzLjQzNzggMzAuNDYyMSAxMy45Mzc4IDMwLjQ2MjEgMTQuMzQyOCAzMC4yMjU4TDI0LjA2NjggMjQuNjEwN1YyOC40OTgzQzI0LjA2OTMgMjguNTM4MyAyNC4wNTA1IDI4LjU3NyAyNC4wMTkzIDI4LjYwMkwxNS45Njc5IDMzLjI1MDlDMTIuMzgxNSAzNS4zMTU5IDcuODAxNDQgMzQuMDg4NCA1LjcyNzY1IDMwLjUwNzFINS43MjY0Wk0zLjYzMDEgMTMuMTIwNUM0LjUwNTEyIDExLjYwMDQgNS44ODY0IDEwLjQzNzkgNy41MzE0NCA5LjgzNDE1QzcuNTMxNDQgOS45MDI5IDcuNTI3NjkgMTAuMDI0MiA3LjUyNzY5IDEwLjEwOTJWMTkuMzEwNkM3LjUyNTE5IDE5Ljc3ODEgNy43NzUxOSAyMC4yMTE5IDguMTgxNDUgMjAuNDQzMUwxNy45MDU0IDI2LjA1N0wxNC41MzkxIDI4LjAwMDhDMTQuNTA1MyAyOC4wMjMzIDE0LjQ2MjggMjguMDI3IDE0LjQyNTMgMjguMDEwOEw2LjM3MjY2IDIzLjM1ODJDMi43OTM4MyAyMS4yODU2IDEuNTY2MzEgMTYuNzA2OCAzLjYyODg1IDEzLjEyMTdMMy42MzAxIDEzLjEyMDVaTTMxLjI4ODIgMTkuNTU2OUwyMS41NjQyIDEzLjk0MTdMMjQuOTMwNiAxMS45OTkyQzI0Ljk2NDMgMTEuOTc2NyAyNS4wMDY4IDExLjk3MjkgMjUuMDQ0MyAxMS45ODkyTDMzLjA5NyAxNi42MzhDMzYuNjgyMSAxOC43MDkzIDM3LjkxMDggMjMuMjk1NyAzNS44Mzk1IDI2Ljg4MDhDMzQuOTYzMyAyOC4zOTgzIDMzLjU4MzIgMjkuNTYwOCAzMS45Mzk1IDMwLjE2NThWMjAuNjg5NEMzMS45NDMyIDIwLjIyMTkgMzEuNjk0NSAxOS43ODk0IDMxLjI4OTQgMTkuNTU2OUgzMS4yODgyWk0zNC42MzgzIDE0LjUxNDJDMzQuNTc5NSAxNC40NzggMzQuNDc1OCAxNC40MTU1IDM0LjQwMiAxNC4zNzNMMjYuNDM2OCA5Ljc3Mjg5QzI2LjAzMzEgOS41MzY2NCAyNS41MzMxIDkuNTM2NjQgMjUuMTI4MSA5Ljc3Mjg5TDE1LjQwNDEgMTUuMzg4VjExLjUwMDRDMTUuNDAxNiAxMS40NjA0IDE1LjQyMDQgMTEuNDIxNyAxNS40NTE2IDExLjM5NjdMMjMuNTAzIDYuNzUxNThDMjcuMDg5NCA0LjY4Mjc5IDMxLjY3NDUgNS45MTQwNiAzMy43NDIgOS41MDE2NEMzNC42MTU4IDExLjAxNjcgMzQuOTMyIDEyLjc5MDUgMzQuNjM1OCAxNC41MTQySDM0LjYzODNaTTEzLjU3NDEgMjEuNDQzMUwxMC4yMDY1IDE5LjQ5OTRDMTAuMTcwMiAxOS40ODE5IDEwLjE0NjUgMTkuNDQ2OCAxMC4xNDE1IDE5LjQwNjhWMTAuMTA3OUMxMC4xNDQgNS45Njc4MSAxMy41MDI4IDIuNjEyNzQgMTcuNjQyOSAyLjYxNTI0QzE5LjM5NDIgMi42MTUyNCAyMS4wODkyIDMuMjMwMjUgMjIuNDM1NSA0LjM1MDI4QzIyLjM3NDMgNC4zODI3OCAyMi4yNjkzIDQuNDQxNTMgMjIuMTk5MiA0LjQ4NDAzTDE0LjIzNDEgOS4wODQxM0MxMy44MjY2IDkuMzE1MzggMTMuNTc2NiA5Ljc0Nzg5IDEzLjU3OTEgMTAuMjE2N0wxMy41NzQxIDIxLjQ0MDZWMjEuNDQzMVpNMTUuNDAyOSAxNy41MDA2TDE5LjczNDIgMTQuOTk5M0wyNC4wNjU1IDE3LjQ5OTNWMjIuNTAwN0wxOS43MzQyIDI1LjAwMDdMMTUuNDAyOSAyMi41MDA3VjE3LjUwMDZaIiBmaWxsPSIjN0Q3RDg3Ii8+Cjwvc3ZnPgo="},"displayName":"OpenAI Chat Model","typeVersion":1,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]}],"categories":[{"id":16,"name":"DevOps"},{"id":49,"name":"AI Summarization"}],"image":[]}}