feat(02-02): add Docker query branch to workflow
- Add Switch node to route messages based on content - Add Execute Command node with curl to Docker API - Add Parse and Match code node with fuzzy matching - Add Format Response code node with emoji mapping - Preserve echo branch for non-docker messages - Handle summary, single match, multiple matches, and errors
This commit is contained in:
+148
-3
@@ -46,6 +46,98 @@
|
||||
"typeVersion": 2,
|
||||
"position": [460, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"id": "docker-query-route",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": false,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "contains-status",
|
||||
"leftValue": "={{ $json.message.text.toLowerCase() }}",
|
||||
"rightValue": "status",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "contains"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "or"
|
||||
},
|
||||
"renameOutput": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"fallbackOutput": "extra"
|
||||
}
|
||||
},
|
||||
"id": "switch-route",
|
||||
"name": "Route Message",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3.2,
|
||||
"position": [680, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "curl --unix-socket /var/run/docker.sock 'http://localhost/v1.47/containers/json?all=true'",
|
||||
"options": {}
|
||||
},
|
||||
"id": "exec-docker-list",
|
||||
"name": "Docker List Containers",
|
||||
"type": "n8n-nodes-base.executeCommand",
|
||||
"typeVersion": 1,
|
||||
"position": [900, 200]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get Docker API response and user input\nconst dockerOutput = $input.item.json.stdout;\nconst userMessage = $('Telegram Trigger').item.json.message.text.toLowerCase().trim();\nconst chatId = $('Telegram Trigger').item.json.message.chat.id;\n\n// Check for Docker connection error\nif (!dockerOutput || $input.item.json.stderr) {\n return [{\n json: {\n chatId: chatId,\n error: true,\n text: \"❌ Can't reach Docker — check if n8n has socket access\"\n }\n }];\n}\n\n// Parse JSON response\nlet containers;\ntry {\n containers = JSON.parse(dockerOutput);\n} catch (e) {\n return [{\n json: {\n chatId: chatId,\n error: true,\n text: \"❌ Unexpected Docker response — failed to parse JSON\"\n }\n }];\n}\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '') // Remove leading slash\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '') // Remove common prefixes\n .toLowerCase();\n}\n\n// Extract container name from user query\n// Remove common query words to get the container name\nconst queryWords = ['status', 'show', 'check', 'container', 'docker', 'what', 'is', 'the', 'of', 'for'];\nconst words = userMessage.split(/\\s+/).filter(word => !queryWords.includes(word));\nconst requestedName = words.join(' ').trim();\n\n// If no container name specified, return summary\nif (!requestedName || requestedName === '') {\n const counts = containers.reduce((acc, c) => {\n acc[c.State] = (acc[c.State] || 0) + 1;\n return acc;\n }, {});\n\n const parts = [];\n if (counts.running) parts.push(`${counts.running} running`);\n if (counts.exited) parts.push(`${counts.exited} stopped`);\n if (counts.paused) parts.push(`${counts.paused} paused`);\n if (counts.restarting) parts.push(`${counts.restarting} restarting`);\n\n const summary = parts.length > 0 ? parts.join(', ') : 'No containers found';\n\n return [{\n json: {\n chatId: chatId,\n summary: true,\n containers: containers,\n text: `📊 Container summary: ${summary}`\n }\n }];\n}\n\n// Find matching containers using fuzzy matching\nconst matches = containers.filter(c => {\n const containerName = normalizeName(c.Names[0]);\n const normalized = requestedName.toLowerCase();\n return containerName.includes(normalized) || normalized.includes(containerName);\n});\n\n// Handle no matches\nif (matches.length === 0) {\n return [{\n json: {\n chatId: chatId,\n error: true,\n text: `❌ No container found matching \"${requestedName}\".\\n\\nTry \"status\" to see all containers.`\n }\n }];\n}\n\n// Handle multiple matches\nif (matches.length > 1) {\n const names = matches.map(c => c.Names[0].replace(/^\\//, '')).join('\\n• ');\n return [{\n json: {\n chatId: chatId,\n multipleMatches: true,\n matches: matches,\n text: `🔍 Found ${matches.length} matches:\\n\\n• ${names}\\n\\nPlease be more specific.`\n }\n }];\n}\n\n// Single match - return container details\nconst container = matches[0];\nreturn [{\n json: {\n chatId: chatId,\n singleMatch: true,\n container: {\n id: container.Id,\n name: container.Names[0].replace(/^\\//, ''),\n state: container.State,\n status: container.Status,\n image: container.Image\n }\n }\n}];"
|
||||
},
|
||||
"id": "code-parse-match",
|
||||
"name": "Parse and Match",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1120, 200]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get the data from previous node\nconst data = $input.item.json;\nconst chatId = data.chatId;\n\n// If error or summary, pass through as-is\nif (data.error || data.summary || data.multipleMatches) {\n return [{\n json: {\n chatId: chatId,\n text: data.text\n }\n }];\n}\n\n// Format single container details\nif (data.singleMatch) {\n const container = data.container;\n\n // Emoji mapping for states\n const stateEmoji = {\n 'running': '✅',\n 'exited': '❌',\n 'paused': '⏸️',\n 'restarting': '🔄',\n 'dead': '💀'\n };\n\n const emoji = stateEmoji[container.state] || '❓';\n\n // Format detailed response\n const text = `${emoji} <b>${container.name}</b>\\n\\n` +\n `<b>State:</b> ${container.state}\\n` +\n `<b>Status:</b> ${container.status}\\n` +\n `<b>Image:</b> ${container.image}\\n` +\n `<b>ID:</b> ${container.id.substring(0, 12)}`;\n\n return [{\n json: {\n chatId: chatId,\n text: text\n }\n }];\n}\n\n// Fallback\nreturn [{\n json: {\n chatId: chatId,\n text: \"❌ Unexpected response format\"\n }\n}];"
|
||||
},
|
||||
"id": "code-format-response",
|
||||
"name": "Format Response",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1340, 200]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"operation": "sendMessage",
|
||||
"chatId": "={{ $json.chatId }}",
|
||||
"text": "={{ $json.text }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "HTML"
|
||||
}
|
||||
},
|
||||
"id": "telegram-send-docker",
|
||||
"name": "Send Docker Response",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [1560, 200],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const message = $input.item.json.message;\nconst timestamp = new Date().toISOString();\nconst text = message.text || '(no text)';\n\nreturn {\n json: {\n chatId: message.chat.id,\n text: `Got: ${text}\\n\\nProcessed: ${timestamp}`\n }\n};"
|
||||
@@ -54,7 +146,7 @@
|
||||
"name": "Format Echo",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [680, 240]
|
||||
"position": [900, 400]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -70,7 +162,7 @@
|
||||
"name": "Send Echo",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [900, 240],
|
||||
"position": [1120, 400],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
@@ -95,7 +187,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Format Echo",
|
||||
"node": "Route Message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -103,6 +195,59 @@
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Route Message": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Docker List Containers",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "Format Echo",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Docker List Containers": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse and Match",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse and Match": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Format Response",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Format Response": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Docker Response",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Format Echo": {
|
||||
"main": [
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user