diff --git a/n8n-workflow.json b/n8n-workflow.json index 65332c5..1ed2a1f 100644 --- a/n8n-workflow.json +++ b/n8n-workflow.json @@ -3,13 +3,19 @@ "nodes": [ { "parameters": { - "updates": ["message", "callback_query"] + "updates": [ + "message", + "callback_query" + ] }, "id": "telegram-trigger", "name": "Telegram Trigger", "type": "n8n-nodes-base.telegramTrigger", "typeVersion": 1.1, - "position": [240, 300], + "position": [ + 240, + 300 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -79,7 +85,10 @@ "name": "Route Update Type", "type": "n8n-nodes-base.switch", "typeVersion": 3.2, - "position": [460, 300] + "position": [ + 460, + 300 + ] }, { "parameters": { @@ -108,7 +117,10 @@ "name": "IF User Authenticated", "type": "n8n-nodes-base.if", "typeVersion": 2, - "position": [680, 200] + "position": [ + 680, + 200 + ] }, { "parameters": { @@ -137,7 +149,10 @@ "name": "IF Callback Authenticated", "type": "n8n-nodes-base.if", "typeVersion": 2, - "position": [680, 500] + "position": [ + 680, + 500 + ] }, { "parameters": { @@ -229,6 +244,29 @@ "combinator": "or" }, "renameOutput": false + }, + { + "id": "logs-command-route", + "conditions": { + "options": { + "caseSensitive": false, + "leftValue": "", + "typeValidation": "loose" + }, + "conditions": [ + { + "id": "matches-logs", + "leftValue": "={{ $json.message.text.toLowerCase().trim() }}", + "rightValue": "^(show\\s+)?logs\\s+", + "operator": { + "type": "string", + "operation": "regex" + } + } + ], + "combinator": "or" + }, + "renameOutput": false } ] }, @@ -240,7 +278,10 @@ "name": "Route Message", "type": "n8n-nodes-base.switch", "typeVersion": 3.2, - "position": [900, 200] + "position": [ + 900, + 200 + ] }, { "parameters": { @@ -251,7 +292,10 @@ "name": "Docker List Containers", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [1120, 100] + "position": [ + 1120, + 100 + ] }, { "parameters": { @@ -261,7 +305,10 @@ "name": "Parse and Match", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1340, 100] + "position": [ + 1340, + 100 + ] }, { "parameters": { @@ -271,7 +318,10 @@ "name": "Format Response", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1560, 100] + "position": [ + 1560, + 100 + ] }, { "parameters": { @@ -287,7 +337,10 @@ "name": "Send Docker Response", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [1560, 200], + "position": [ + 1560, + 200 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -303,7 +356,10 @@ "name": "Format Echo", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [900, 800] + "position": [ + 900, + 800 + ] }, { "parameters": { @@ -319,7 +375,10 @@ "name": "Send Echo", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [1120, 800], + "position": [ + 1120, + 800 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -335,7 +394,10 @@ "name": "Parse Action", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [900, 400] + "position": [ + 900, + 400 + ] }, { "parameters": { @@ -346,7 +408,10 @@ "name": "Docker List for Action", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [1120, 400] + "position": [ + 1120, + 400 + ] }, { "parameters": { @@ -356,7 +421,10 @@ "name": "Match Container", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1340, 400] + "position": [ + 1340, + 400 + ] }, { "parameters": { @@ -464,7 +532,10 @@ "name": "Check Match Count", "type": "n8n-nodes-base.switch", "typeVersion": 3.2, - "position": [1560, 400] + "position": [ + 1560, + 400 + ] }, { "parameters": { @@ -474,7 +545,10 @@ "name": "Build Action Command", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1780, 400] + "position": [ + 1780, + 400 + ] }, { "parameters": { @@ -485,7 +559,10 @@ "name": "Execute Action", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [2000, 400] + "position": [ + 2000, + 400 + ] }, { "parameters": { @@ -495,7 +572,10 @@ "name": "Parse Action Result", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [2220, 400] + "position": [ + 2220, + 400 + ] }, { "parameters": { @@ -511,7 +591,10 @@ "name": "Send Action Result", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [2440, 400], + "position": [ + 2440, + 400 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -527,7 +610,10 @@ "name": "Find Closest Match", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1780, 300] + "position": [ + 1780, + 300 + ] }, { "parameters": { @@ -556,7 +642,10 @@ "name": "Check Suggestion", "type": "n8n-nodes-base.if", "typeVersion": 2, - "position": [2000, 300] + "position": [ + 2000, + 300 + ] }, { "parameters": { @@ -566,7 +655,10 @@ "name": "Build Suggestion Keyboard", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [2220, 200] + "position": [ + 2220, + 200 + ] }, { "parameters": { @@ -581,7 +673,10 @@ "name": "Send Suggestion", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [2440, 200], + "position": [ + 2440, + 200 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -603,7 +698,10 @@ "name": "Send No Match", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [2220, 400], + "position": [ + 2220, + 400 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -619,7 +717,10 @@ "name": "Build Batch Keyboard", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1780, 500] + "position": [ + 1780, + 500 + ] }, { "parameters": { @@ -634,7 +735,10 @@ "name": "Send Batch Confirmation", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [2000, 500], + "position": [ + 2000, + 500 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -656,7 +760,10 @@ "name": "Send Docker Error", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [1780, 200], + "position": [ + 1780, + 200 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -672,7 +779,10 @@ "name": "Parse Callback Data", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [900, 500] + "position": [ + 900, + 500 + ] }, { "parameters": { @@ -760,7 +870,10 @@ "name": "Route Callback", "type": "n8n-nodes-base.switch", "typeVersion": 3.2, - "position": [1120, 500] + "position": [ + 1120, + 500 + ] }, { "parameters": { @@ -770,7 +883,10 @@ "name": "Handle Cancel", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1340, 500] + "position": [ + 1340, + 500 + ] }, { "parameters": { @@ -785,7 +901,10 @@ "name": "Answer Cancel Query", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [1560, 500], + "position": [ + 1560, + 500 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -806,7 +925,10 @@ "name": "Delete Cancel Message", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [1780, 500], + "position": [ + 1780, + 500 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -822,7 +944,10 @@ "name": "Handle Expired", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1340, 600] + "position": [ + 1340, + 600 + ] }, { "parameters": { @@ -837,7 +962,10 @@ "name": "Answer Expired Query", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [1560, 600], + "position": [ + 1560, + 600 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -858,7 +986,10 @@ "name": "Delete Expired Message", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [1780, 600], + "position": [ + 1780, + 600 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -874,7 +1005,10 @@ "name": "Build Callback Action", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1340, 700] + "position": [ + 1340, + 700 + ] }, { "parameters": { @@ -885,7 +1019,10 @@ "name": "Execute Callback Action", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [1560, 700] + "position": [ + 1560, + 700 + ] }, { "parameters": { @@ -895,7 +1032,10 @@ "name": "Parse Callback Result", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1780, 700] + "position": [ + 1780, + 700 + ] }, { "parameters": { @@ -910,7 +1050,10 @@ "name": "Answer Action Query", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [2000, 700], + "position": [ + 2000, + 700 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -931,7 +1074,10 @@ "name": "Delete Suggestion Message", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [2220, 700], + "position": [ + 2220, + 700 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -953,7 +1099,10 @@ "name": "Send Callback Result", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [2440, 700], + "position": [ + 2440, + 700 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -969,7 +1118,10 @@ "name": "Build Batch Commands", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1340, 800] + "position": [ + 1340, + 800 + ] }, { "parameters": { @@ -979,7 +1131,10 @@ "name": "Prepare Batch Execution", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1560, 800] + "position": [ + 1560, + 800 + ] }, { "parameters": { @@ -990,7 +1145,10 @@ "name": "Execute Batch Action", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [1780, 800] + "position": [ + 1780, + 800 + ] }, { "parameters": { @@ -1000,7 +1158,10 @@ "name": "Parse Batch Result", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [2000, 800] + "position": [ + 2000, + 800 + ] }, { "parameters": { @@ -1010,7 +1171,10 @@ "name": "Format Batch Result", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [2220, 800] + "position": [ + 2220, + 800 + ] }, { "parameters": { @@ -1025,7 +1189,10 @@ "name": "Answer Batch Query", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [2440, 800], + "position": [ + 2440, + 800 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -1046,7 +1213,10 @@ "name": "Delete Batch Confirm Message", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [2660, 800], + "position": [ + 2660, + 800 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -1068,7 +1238,10 @@ "name": "Send Batch Result", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [2880, 800], + "position": [ + 2880, + 800 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -1084,7 +1257,10 @@ "name": "Parse Update Command", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [900, 1000] + "position": [ + 900, + 1000 + ] }, { "parameters": { @@ -1095,7 +1271,10 @@ "name": "Docker List for Update", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [1120, 1000] + "position": [ + 1120, + 1000 + ] }, { "parameters": { @@ -1105,7 +1284,10 @@ "name": "Match Update Container", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1340, 1000] + "position": [ + 1340, + 1000 + ] }, { "parameters": { @@ -1217,7 +1399,10 @@ "name": "Check Update Match Count", "type": "n8n-nodes-base.switch", "typeVersion": 3.2, - "position": [1560, 1000] + "position": [ + 1560, + 1000 + ] }, { "parameters": { @@ -1233,7 +1418,10 @@ "name": "Send Update Error", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [1780, 900], + "position": [ + 1780, + 900 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -1249,7 +1437,10 @@ "name": "Handle Update Multiple", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1780, 1100] + "position": [ + 1780, + 1100 + ] }, { "parameters": { @@ -1265,7 +1456,10 @@ "name": "Send Update Multiple", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [2000, 1100], + "position": [ + 2000, + 1100 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -1287,7 +1481,10 @@ "name": "Send Update No Match", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [1780, 1000], + "position": [ + 1780, + 1000 + ], "credentials": { "telegramApi": { "id": "telegram-credential", @@ -1303,7 +1500,10 @@ "name": "Build Inspect Command", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [1780, 1200] + "position": [ + 1780, + 1200 + ] }, { "parameters": { @@ -1314,7 +1514,10 @@ "name": "Inspect Container", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [2000, 1200] + "position": [ + 2000, + 1200 + ] }, { "parameters": { @@ -1324,7 +1527,10 @@ "name": "Parse Container Config", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [2220, 1200] + "position": [ + 2220, + 1200 + ] }, { "parameters": { @@ -1334,7 +1540,10 @@ "name": "Build Pull Command", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [2440, 1200] + "position": [ + 2440, + 1200 + ] }, { "parameters": { @@ -1345,7 +1554,10 @@ "name": "Pull Image", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [2660, 1200] + "position": [ + 2660, + 1200 + ] }, { "parameters": { @@ -1355,7 +1567,10 @@ "name": "Build Image Inspect", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [2880, 1200] + "position": [ + 2880, + 1200 + ] }, { "parameters": { @@ -1366,7 +1581,10 @@ "name": "Inspect New Image", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [3100, 1200] + "position": [ + 3100, + 1200 + ] }, { "parameters": { @@ -1376,7 +1594,10 @@ "name": "Compare Digests", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [3320, 1200] + "position": [ + 3320, + 1200 + ] }, { "parameters": { @@ -1405,7 +1626,10 @@ "name": "Check If Update Needed", "type": "n8n-nodes-base.if", "typeVersion": 2, - "position": [3540, 1200] + "position": [ + 3540, + 1200 + ] }, { "parameters": { @@ -1415,7 +1639,10 @@ "name": "Build Stop Command", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [3760, 1200] + "position": [ + 3760, + 1200 + ] }, { "parameters": { @@ -1426,7 +1653,10 @@ "name": "Stop Container", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [3980, 1200] + "position": [ + 3980, + 1200 + ] }, { "parameters": { @@ -1436,7 +1666,10 @@ "name": "Verify Stop Build Remove", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [4200, 1200] + "position": [ + 4200, + 1200 + ] }, { "parameters": { @@ -1447,7 +1680,10 @@ "name": "Remove Container", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [4420, 1200] + "position": [ + 4420, + 1200 + ] }, { "parameters": { @@ -1457,7 +1693,10 @@ "name": "Build Create Body", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [4640, 1200] + "position": [ + 4640, + 1200 + ] }, { "parameters": { @@ -1467,7 +1706,10 @@ "name": "Build Create Command", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [4860, 1200] + "position": [ + 4860, + 1200 + ] }, { "parameters": { @@ -1478,7 +1720,10 @@ "name": "Create Container", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [5080, 1200] + "position": [ + 5080, + 1200 + ] }, { "parameters": { @@ -1488,7 +1733,10 @@ "name": "Parse Create Response", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [5300, 1200] + "position": [ + 5300, + 1200 + ] }, { "parameters": { @@ -1498,7 +1746,10 @@ "name": "Build Start Command", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [5520, 1200] + "position": [ + 5520, + 1200 + ] }, { "parameters": { @@ -1509,7 +1760,10 @@ "name": "Start New Container", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, - "position": [5740, 1200] + "position": [ + 5740, + 1200 + ] }, { "parameters": { @@ -1519,7 +1773,10 @@ "name": "Format Update Result", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [5960, 1200] + "position": [ + 5960, + 1200 + ] }, { "parameters": { @@ -1535,13 +1792,281 @@ "name": "Send Update Result", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [6180, 1200], + "position": [ + 6180, + 1200 + ], "credentials": { "telegramApi": { "id": "telegram-credential", "name": "Telegram API" } } + }, + { + "parameters": { + "jsCode": "// Parse logs command from message\nconst text = $json.message.text.toLowerCase().trim();\nconst chatId = $json.message.chat.id;\nconst messageId = $json.message.message_id;\n\n// Match logs pattern: (show )logs (optional line count)\n// Examples:\n// - \"logs plex\" -> { container: \"plex\", lines: 50 }\n// - \"show logs sonarr\" -> { container: \"sonarr\", lines: 50 }\n// - \"logs nginx 100\" -> { container: \"nginx\", lines: 100 }\n// - \"show logs radarr last 200\" -> { container: \"radarr\", lines: 200 }\n\nconst match = text.match(/^(?:show\\s+)?logs\\s+(.+?)(?:\\s+(?:last\\s+)?(\\d+))?$/i);\n\nif (!match) {\n return {\n json: {\n error: true,\n errorMessage: 'Invalid logs format. Use: logs [line-count]',\n chatId: chatId\n }\n };\n}\n\nconst containerQuery = match[1].trim();\nconst lines = match[2] ? parseInt(match[2], 10) : 50;\n\n// Validate line count (reasonable limits)\nconst validatedLines = Math.min(Math.max(lines, 1), 1000);\n\nreturn {\n json: {\n containerQuery: containerQuery,\n lines: validatedLines,\n chatId: chatId,\n messageId: messageId\n }\n};" + }, + "id": "code-parse-logs", + "name": "Parse Logs Command", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 900, + 600 + ] + }, + { + "parameters": { + "command": "curl -s --unix-socket /var/run/docker.sock 'http://localhost/v1.47/containers/json?all=true'", + "options": {} + }, + "id": "exec-docker-list-logs", + "name": "Docker List for Logs", + "type": "n8n-nodes-base.executeCommand", + "typeVersion": 1, + "position": [ + 1120, + 600 + ] + }, + { + "parameters": { + "jsCode": "// Get Docker API response and logs info from Parse Logs\nconst dockerOutput = $input.item.json.stdout;\nconst logsData = $('Parse Logs Command').item.json;\nconst containerQuery = logsData.containerQuery;\nconst lines = logsData.lines;\nconst chatId = logsData.chatId;\n\n// Parse JSON response\nlet containers;\ntry {\n if (!dockerOutput || dockerOutput.trim() === '') {\n throw new Error('Empty response');\n }\n containers = JSON.parse(dockerOutput);\n} catch (e) {\n return [{\n json: {\n matchCount: -1,\n error: true,\n chatId: chatId,\n text: \"Can't reach Docker - check if n8n has socket access\"\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// Find matching containers using fuzzy matching\nconst matches = containers.filter(c => {\n const containerName = normalizeName(c.Names[0]);\n const normalized = containerQuery.toLowerCase();\n return containerName.includes(normalized) || normalized.includes(containerName);\n});\n\n// Return match results with all necessary context\nreturn [{\n json: {\n matches: matches.map(c => ({\n Id: c.Id,\n Name: c.Names[0].replace(/^\\//, ''),\n State: c.State\n })),\n matchCount: matches.length,\n containerQuery: containerQuery,\n lines: lines,\n chatId: chatId\n }\n}];" + }, + "id": "code-match-logs-container", + "name": "Match Logs Container", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1340, + 600 + ] + }, + { + "parameters": { + "rules": { + "values": [ + { + "id": "logs-docker-error", + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "count-negative", + "leftValue": "={{ $json.matchCount }}", + "rightValue": "0", + "operator": { + "type": "number", + "operation": "lt" + } + } + ], + "combinator": "and" + }, + "renameOutput": false + }, + { + "id": "logs-no-match", + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "count-zero", + "leftValue": "={{ $json.matchCount }}", + "rightValue": "0", + "operator": { + "type": "number", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": false + }, + { + "id": "logs-single-match", + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "count-one", + "leftValue": "={{ $json.matchCount }}", + "rightValue": "1", + "operator": { + "type": "number", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": false + }, + { + "id": "logs-multiple-matches", + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "count-many", + "leftValue": "={{ $json.matchCount }}", + "rightValue": "1", + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "renameOutput": false + } + ] + }, + "options": {} + }, + "id": "switch-logs-match-count", + "name": "Check Logs Match Count", + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 1560, + 600 + ] + }, + { + "parameters": { + "jsCode": "// Build Docker logs API curl command\nconst matchData = $input.item.json;\nconst container = matchData.matches[0]; // Single match verified by switch\nconst lines = matchData.lines;\n\nconst cmd = `curl -s --unix-socket /var/run/docker.sock \"http://localhost/v1.47/containers/${container.Id}/logs?stdout=1&stderr=1&tail=${lines}×tamps=1\"`;\n\nreturn {\n json: {\n command: cmd,\n containerName: container.Name,\n containerId: container.Id,\n lines: lines,\n chatId: matchData.chatId\n }\n};" + }, + "id": "code-build-logs-cmd", + "name": "Build Logs Command", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1780, + 500 + ] + }, + { + "parameters": { + "command": "={{ $json.command }}", + "options": {} + }, + "id": "exec-logs", + "name": "Execute Logs", + "type": "n8n-nodes-base.executeCommand", + "typeVersion": 1, + "position": [ + 2000, + 500 + ] + }, + { + "parameters": { + "jsCode": "// Format Docker logs for Telegram\nconst rawOutput = $input.item.json.stdout || '';\nconst containerName = $('Build Logs Command').item.json.containerName;\nconst requestedLines = $('Build Logs Command').item.json.lines;\nconst chatId = $('Build Logs Command').item.json.chatId;\n\n// Handle empty logs\nif (!rawOutput || rawOutput.trim() === '') {\n return {\n json: {\n chatId: chatId,\n text: `No logs available for ${containerName}`\n }\n };\n}\n\n// Docker API with timestamps returns text lines when using tail parameter\n// But may have 8-byte binary headers we need to strip\nconst lines = rawOutput.split('\\n')\n .filter(line => line.length > 0)\n .map(line => {\n // Check if line starts with binary header (non-printable chars in first 8 bytes)\n if (line.length > 8 && line.charCodeAt(0) <= 2) {\n return line.substring(8);\n }\n return line;\n })\n .join('\\n');\n\n// Truncate for Telegram (4096 char limit, leave room for header)\nconst maxLen = 3800;\nconst truncated = lines.length > maxLen\n ? lines.substring(0, maxLen) + '\\n... (truncated)'\n : lines;\n\nconst lineCount = lines.split('\\n').length;\nconst header = `Logs for ${containerName} (last ${lineCount} lines):\\n\\n`;\n\nreturn {\n json: {\n chatId: chatId,\n text: header + '
' + truncated + '
'\n }\n};" + }, + "id": "code-format-logs", + "name": "Format Logs", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2220, + 500 + ] + }, + { + "parameters": { + "resource": "message", + "operation": "sendMessage", + "chatId": "={{ $json.chatId }}", + "text": "={{ $json.text }}", + "additionalFields": { + "parse_mode": "HTML" + } + }, + "id": "telegram-send-logs", + "name": "Send Logs Response", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.2, + "position": [ + 2440, + 500 + ], + "credentials": { + "telegramApi": { + "id": "telegram-credential", + "name": "Telegram API" + } + } + }, + { + "parameters": { + "resource": "message", + "operation": "sendMessage", + "chatId": "={{ $json.chatId }}", + "text": "={{ $json.text || 'Error retrieving logs' }}", + "additionalFields": { + "parse_mode": "HTML" + } + }, + "id": "telegram-send-logs-error", + "name": "Send Logs Error", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.2, + "position": [ + 1780, + 700 + ], + "credentials": { + "telegramApi": { + "id": "telegram-credential", + "name": "Telegram API" + } + } + }, + { + "parameters": { + "jsCode": "const data = $input.item.json;\nreturn {\n json: {\n chatId: data.chatId,\n text: `No container found matching \"${data.containerQuery}\".\\n\\nTry \"status\" to see all containers.`\n }\n};" + }, + "id": "code-format-logs-no-match", + "name": "Format Logs No Match", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1780, + 650 + ] + }, + { + "parameters": { + "jsCode": "const data = $input.item.json;\nconst names = data.matches.map(m => m.Name).join('\\n- ');\nreturn {\n json: {\n chatId: data.chatId,\n text: `Found ${data.matches.length} containers matching \"${data.containerQuery}\":\\n\\n- ${names}\\n\\nPlease be more specific.`\n }\n};" + }, + "id": "code-format-logs-multiple", + "name": "Format Logs Multiple", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1780, + 600 + ] } ], "connections": { @@ -2340,6 +2865,137 @@ } ] ] + }, + "switch-route": { + "main": [ + [ + { + "node": "Parse Logs Command", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-parse-logs": { + "main": [ + [ + { + "node": "Docker List for Logs", + "type": "main", + "index": 0 + } + ] + ] + }, + "exec-docker-list-logs": { + "main": [ + [ + { + "node": "Match Logs Container", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-match-logs-container": { + "main": [ + [ + { + "node": "Check Logs Match Count", + "type": "main", + "index": 0 + } + ] + ] + }, + "switch-logs-match-count": { + "main": [ + [ + { + "node": "Send Logs Error", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Format Logs No Match", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Build Logs Command", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Format Logs Multiple", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-build-logs-cmd": { + "main": [ + [ + { + "node": "Execute Logs", + "type": "main", + "index": 0 + } + ] + ] + }, + "exec-logs": { + "main": [ + [ + { + "node": "Format Logs", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-format-logs": { + "main": [ + [ + { + "node": "Send Logs Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-format-logs-no-match": { + "main": [ + [ + { + "node": "Send Logs Error", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-format-logs-multiple": { + "main": [ + [ + { + "node": "Send Logs Error", + "type": "main", + "index": 0 + } + ] + ] } }, "pinData": {}, @@ -2350,4 +3006,4 @@ "tags": [], "triggerCount": 1, "active": false -} +} \ No newline at end of file