feat(10-05): extract logs flow to Container Logs sub-workflow
Created n8n-container-logs.json: - Execute Workflow Trigger entry point - Parse and validate input (containerId/Name, lineCount, chatId, messageId) - Query Docker to find container by name if needed - Execute docker logs command - Format output for Telegram (HTML escape, truncate, add header) - Return success/message/containerName/lineCount Updated main workflow: - Add Prepare Text Logs Input (text command path) - Add Execute Text Logs sub-workflow node - Add Prepare Inline Logs Input (inline keyboard path) - Add Execute Inline Logs sub-workflow node - Add Format Inline Logs Result (adds keyboard) - Remove 14 obsolete inline logs nodes - Node count: 208 -> 199 (-9) Sub-workflow has placeholder ID - will be updated after n8n import in Task 4.
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
{
|
||||
"name": "Container Logs",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "711c56f9-46d5-41dd-aa5c-e7e4230793f3",
|
||||
"name": "Execute Workflow Trigger",
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
240,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse and validate input\nconst input = $json;\n\n// Get container identifier (ID or name)\nconst containerId = input.containerId || '';\nconst containerName = input.containerName || '';\nconst lineCount = input.lineCount || 50;\nconst chatId = input.chatId;\nconst messageId = input.messageId || 0;\nconst responseMode = input.responseMode || 'text';\n\nif (!containerId && !containerName) {\n throw new Error('Either containerId or containerName required');\n}\n\nif (!chatId) {\n throw new Error('chatId required');\n}\n\nreturn {\n json: {\n containerId: containerId,\n containerName: containerName,\n lineCount: Math.min(Math.max(parseInt(lineCount), 1), 1000),\n chatId: chatId,\n messageId: messageId,\n responseMode: responseMode\n }\n};"
|
||||
},
|
||||
"id": "dac65a64-173a-4536-b3c5-0699704380fa",
|
||||
"name": "Parse Input",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
460,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get container ID if needed\nconst data = $json;\n\n// If we already have container ID, pass through\nif (data.containerId) {\n return {\n json: {\n ...data,\n useDirectId: true\n }\n };\n}\n\n// Otherwise, need to query Docker to find by name\nreturn {\n json: {\n ...data,\n useDirectId: false,\n dockerCommand: 'curl -s --max-time 5 \"http://docker-socket-proxy:2375/v1.47/containers/json?all=1\"'\n }\n};"
|
||||
},
|
||||
"id": "d6db7666-5ecd-4200-8231-ffa6307e4c39",
|
||||
"name": "Check Container ID",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
680,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"id": "has-id",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "check-direct",
|
||||
"leftValue": "={{ $json.useDirectId }}",
|
||||
"rightValue": "true",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"fallbackOutput": "extra"
|
||||
}
|
||||
},
|
||||
"id": "818a4c33-e150-423e-a333-adf9843aac05",
|
||||
"name": "Route ID Check",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
900,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "={{ $json.dockerCommand }}"
|
||||
},
|
||||
"id": "83cbcda1-68c5-46ee-bbb6-2eb5f1ae9077",
|
||||
"name": "Query Docker",
|
||||
"type": "n8n-nodes-base.executeCommand",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1120,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Find container by name\nconst dockerOutput = $input.item.json.stdout;\nconst data = $('Check Container ID').item.json;\nconst containerName = data.containerName.toLowerCase();\n\n// Parse Docker response\nlet containers;\ntry {\n containers = JSON.parse(dockerOutput);\n} catch (e) {\n throw new Error('Failed to parse Docker response');\n}\n\n// Normalize name function\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '')\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '')\n .toLowerCase();\n}\n\n// Find exact match\nconst container = containers.find(c => normalizeName(c.Names[0]) === containerName);\n\nif (!container) {\n throw new Error(`Container \"${containerName}\" not found`);\n}\n\nreturn {\n json: {\n ...data,\n containerId: container.Id,\n containerName: normalizeName(container.Names[0])\n }\n};"
|
||||
},
|
||||
"id": "52dd705b-dd3b-4fdc-8484-276845857ad0",
|
||||
"name": "Find Container",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1340,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build Docker logs command\nconst data = $json;\nconst containerId = data.containerId;\nconst lineCount = data.lineCount;\n\nconst cmd = `curl -s --max-time 10 \"http://docker-socket-proxy:2375/v1.47/containers/${containerId}/logs?stdout=1&stderr=1&tail=${lineCount}×tamps=1\"`;\n\nreturn {\n json: {\n ...data,\n logsCommand: cmd\n }\n};"
|
||||
},
|
||||
"id": "86619651-82f0-459b-a969-d210d2dd6361",
|
||||
"name": "Build Logs Command",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1560,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "={{ $json.logsCommand }}"
|
||||
},
|
||||
"id": "b602549b-e526-4207-a41d-74a936060a25",
|
||||
"name": "Execute Logs",
|
||||
"type": "n8n-nodes-base.executeCommand",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1780,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Format logs output for Telegram\nconst rawOutput = $input.item.json.stdout || '';\nconst data = $('Build Logs Command').item.json;\n\n// HTML escape function\nfunction escapeHtml(text) {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\n// Handle empty logs\nif (!rawOutput || rawOutput.trim() === '') {\n return {\n json: {\n success: true,\n message: `No logs available for <b>${data.containerName}</b>`,\n containerName: data.containerName,\n lineCount: 0\n }\n };\n}\n\n// Strip Docker binary headers and process lines\nconst lines = rawOutput.split('\\n')\n .filter(line => line.length > 0)\n .map(line => {\n // Check if line starts with binary header (8-byte Docker stream header)\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\n// Escape HTML entities\nconst escaped = escapeHtml(truncated);\n\nconst lineCount = lines.split('\\n').length;\nconst header = `Logs for <b>${data.containerName}</b> (last ${lineCount} lines):\\n\\n`;\nconst formatted = header + '<pre>' + escaped + '</pre>';\n\nreturn {\n json: {\n success: true,\n message: formatted,\n containerName: data.containerName,\n lineCount: lineCount\n }\n};"
|
||||
},
|
||||
"id": "e423b026-e619-4e3a-abd9-563e935cd74d",
|
||||
"name": "Format Logs",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2000,
|
||||
300
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Execute Workflow Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Input",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Input": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Container ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check Container ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Route ID Check",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Route ID Check": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Logs Command",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Query Docker",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Query Docker": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Find Container",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Find Container": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Logs Command",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Logs Command": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Logs",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Logs": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Format Logs",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": true,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "c2a9969f-2928-41f9-be03-9692ae242751",
|
||||
"meta": {
|
||||
"instanceId": "unraid-docker-manager"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
Reference in New Issue
Block a user