|
1 | 1 | { |
2 | | - "name": "Technitium DNS -> UniFi Log Insights DNS (Public Template) v0.1.1", |
| 2 | + "name": "Technitium DNS -> UniFi Log Insights DNS (Public Template) v0.1.2", |
3 | 3 | "nodes": [ |
4 | 4 | { |
5 | 5 | "parameters": { |
|
10 | 10 | }, |
11 | 11 | "type": "n8n-nodes-base.webhook", |
12 | 12 | "typeVersion": 2.1, |
13 | | - "position": [ |
14 | | - -1424, |
15 | | - -208 |
16 | | - ], |
| 13 | + "position": [-1424, -208], |
17 | 14 | "id": "92c183f1-3018-41d4-a600-1dadfa8e7ebd", |
18 | 15 | "name": "Webhook" |
19 | 16 | }, |
|
44 | 41 | }, |
45 | 42 | "type": "n8n-nodes-base.if", |
46 | 43 | "typeVersion": 2.3, |
47 | | - "position": [ |
48 | | - -1200, |
49 | | - -208 |
50 | | - ], |
| 44 | + "position": [-1200, -208], |
51 | 45 | "id": "32dc90c4-0891-45d9-9ebf-73297e5b9f27", |
52 | 46 | "name": "If" |
53 | 47 | }, |
|
61 | 55 | }, |
62 | 56 | "type": "n8n-nodes-base.respondToWebhook", |
63 | 57 | "typeVersion": 1.5, |
64 | | - "position": [ |
65 | | - -528, |
66 | | - -304 |
67 | | - ], |
| 58 | + "position": [-528, -304], |
68 | 59 | "id": "05473b99-1c82-4b15-9da9-e6b3a97ebdc7", |
69 | 60 | "name": "Respond to Webhook 'true'" |
70 | 61 | }, |
|
78 | 69 | }, |
79 | 70 | "type": "n8n-nodes-base.respondToWebhook", |
80 | 71 | "typeVersion": 1.5, |
81 | | - "position": [ |
82 | | - -976, |
83 | | - -112 |
84 | | - ], |
| 72 | + "position": [-976, -112], |
85 | 73 | "id": "c515fd27-ea49-49c8-8d24-740c03752a3a", |
86 | 74 | "name": "Respond to Webhook 'forbidden'" |
87 | 75 | }, |
|
91 | 79 | }, |
92 | 80 | "type": "n8n-nodes-base.executeCommand", |
93 | 81 | "typeVersion": 1, |
94 | | - "position": [ |
95 | | - -752, |
96 | | - -304 |
97 | | - ], |
| 82 | + "position": [-752, -304], |
98 | 83 | "id": "d522ddd8-3ae5-4ca9-9b20-4b9d4cd90c88", |
99 | 84 | "name": "Execute Command" |
100 | 85 | }, |
101 | 86 | { |
102 | 87 | "parameters": { |
103 | | - "jsCode": "const inputItem = $input.first();\nconst payload = inputItem.json?.body ?? [];\nconst entries = Array.isArray(payload) ? payload : [payload];\n\nconst blockedTypes = new Set([\"SOA\", \"IXFR\"]);\nconst blockedClientIps = new Set([\n \"192.0.2.10\",\n \"192.0.2.11\",\n \"198.51.100.10\",\n \"172.16.0.1\",\n]);\n\n// RFC3164 BSD timestamp in UTC with space-padded day (\"Apr 4\").\n// The unifi-log-insight container must also run in UTC (its default) so\n// the receiver's parse_syslog_timestamp() interprets these correctly.\nfunction formatRfc3164(ts) {\n const date = ts ? new Date(ts) : new Date();\n if (Number.isNaN(date.getTime())) {\n throw new Error(Invalid timestamp: ${ts});\n }\n const months = ['Jan','Feb','Mar','Apr','May','Jun',\n 'Jul','Aug','Sep','Oct','Nov','Dec'];\n const mon = months[date.getUTCMonth()];\n const day = date.getUTCDate();\n const dd = day < 10 ? ${day} : ${day};\n const hh = String(date.getUTCHours()).padStart(2, '0');\n const mm = String(date.getUTCMinutes()).padStart(2, '0');\n const ss = String(date.getUTCSeconds()).padStart(2, '0');\n return ${mon} ${dd} ${hh}:${mm}:${ss};\n}\n\nfunction tryParseJsonString(value) {\n if (typeof value !== \"string\" || !value.trim()) return null;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\nconst out = [];\n\nfor (const entry of entries) {\n const parsed =\n tryParseJsonString(entry.RenderedMessage) ??\n tryParseJsonString(entry.MessageTemplate) ??\n (entry && typeof entry === \"object\" ? entry : null);\n\n if (!parsed || typeof parsed !== \"object\") {\n out.push({\n json: {\n ok: true,\n forwarded: false,\n skipReason: \"unparsable_log_entry\",\n original: entry,\n },\n });\n continue;\n }\n\n const timestampRaw =\n parsed.timestamp ??\n parsed.Timestamp ??\n entry.Timestamp ??\n null;\n\n const clientIpRaw =\n parsed.clientIp ??\n parsed.clientIPAddress ??\n parsed.client ??\n parsed.remoteAddress ??\n null;\n\n const question = parsed.question ?? {};\n const questionName =\n question.questionName ??\n question.name ??\n parsed.questionName ??\n parsed.domain ??\n null;\n\n const questionType =\n question.questionType ??\n question.type ??\n parsed.questionType ??\n parsed.type ??\n null;\n\n const protocolRaw = String(parsed.protocol ?? \"\").trim();\n const responseCodeRaw = String(parsed.responseCode ?? \"\").trim();\n const responseTypeRaw = String(parsed.responseType ?? \"\").trim();\n\n const normalizedType = String(questionType ?? \"\").trim().toUpperCase();\n const normalizedName = String(questionName ?? \"\").trim();\n\n // Strip optional brackets and port from client IPs (e.g. \"[::1]:5353\" -> \"::1\").\n let normalizedClientIp = String(clientIpRaw ?? \"\").trim();\n const ipMatch = normalizedClientIp.match(/^\\[?([0-9a-fA-F:.]+)\\]?/);\n if (ipMatch) normalizedClientIp = ipMatch[1];\n\n let skipReason = null;\n\n if (!normalizedType) {\n skipReason = \"missing_question_type\";\n } else if (!normalizedName) {\n skipReason = \"missing_question_name\";\n } else if (!normalizedClientIp) {\n skipReason = \"missing_client_ip\";\n } else if (blockedTypes.has(normalizedType)) {\n skipReason = filtered_type_${normalizedType};\n } else if (blockedClientIps.has(normalizedClientIp)) {\n skipReason = filtered_client_${normalizedClientIp};\n }\n\n if (skipReason) {\n out.push({\n json: {\n ok: true,\n forwarded: false,\n skipReason,\n original: parsed,\n originalEntry: entry,\n },\n });\n continue;\n }\n\n const rfc3164Timestamp = formatRfc3164(timestampRaw);\n\n const extras = [\n protocolRaw ? proto=${protocolRaw} : null,\n responseCodeRaw ? rcode=${responseCodeRaw} : null,\n responseTypeRaw ? rtype=${responseTypeRaw} : null,\n ].filter(Boolean).join(\" \");\n\n const syslogLine =\n <14>${rfc3164Timestamp} technitium-dns dnsmasq[1]: +\n query[${normalizedType}] ${normalizedName} from ${normalizedClientIp} +\n (extras ? ${extras} : \"\");\n\n out.push({\n json: {\n ok: true,\n forwarded: true,\n skipReason: null,\n syslogLine,\n parsed: {\n timestampRaw,\n clientIp: normalizedClientIp,\n questionName: normalizedName,\n questionType: normalizedType,\n protocol: protocolRaw || null,\n responseCode: responseCodeRaw || null,\n responseType: responseTypeRaw || null,\n },\n original: parsed,\n originalEntry: entry,\n },\n });\n}\n\nreturn out;\n" |
| 88 | + "jsCode": "const inputItem = $input.first();\nconst payload = inputItem.json?.body ?? [];\nconst entries = Array.isArray(payload) ? payload : [payload];\n\nconst blockedTypes = new Set([\"SOA\", \"IXFR\"]);\nconst blockedClientIps = new Set([\n \"192.0.2.10\",\n \"192.0.2.11\",\n \"198.51.100.10\",\n \"172.16.0.1\",\n]);\n\n// RFC3164 BSD timestamp in UTC with space-padded day (\"Apr 4\").\n// The unifi-log-insight container must also run in UTC (its default) so\n// the receiver's parse_syslog_timestamp() interprets these correctly.\nfunction formatRfc3164(ts) {\n const date = ts ? new Date(ts) : new Date();\n if (Number.isNaN(date.getTime())) {\n throw new Error(`Invalid timestamp: ${ts}`);\n }\n const months = ['Jan','Feb','Mar','Apr','May','Jun',\n 'Jul','Aug','Sep','Oct','Nov','Dec'];\n const mon = months[date.getUTCMonth()];\n const day = date.getUTCDate();\n const dd = day < 10 ? ` ${day}` : `${day}`;\n const hh = String(date.getUTCHours()).padStart(2, '0');\n const mm = String(date.getUTCMinutes()).padStart(2, '0');\n const ss = String(date.getUTCSeconds()).padStart(2, '0');\n return `${mon} ${dd} ${hh}:${mm}:${ss}`;\n}\n\nfunction tryParseJsonString(value) {\n if (typeof value !== \"string\" || !value.trim()) return null;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\nconst out = [];\n\nfor (const entry of entries) {\n const parsed =\n tryParseJsonString(entry.RenderedMessage) ??\n tryParseJsonString(entry.MessageTemplate) ??\n (entry && typeof entry === \"object\" ? entry : null);\n\n if (!parsed || typeof parsed !== \"object\") {\n out.push({\n json: {\n ok: true,\n forwarded: false,\n skipReason: \"unparsable_log_entry\",\n original: entry,\n },\n });\n continue;\n }\n\n const timestampRaw =\n parsed.timestamp ??\n parsed.Timestamp ??\n entry.Timestamp ??\n null;\n\n const clientIpRaw =\n parsed.clientIp ??\n parsed.clientIPAddress ??\n parsed.client ??\n parsed.remoteAddress ??\n null;\n\n const question = parsed.question ?? {};\n const questionName =\n question.questionName ??\n question.name ??\n parsed.questionName ??\n parsed.domain ??\n null;\n\n const questionType =\n question.questionType ??\n question.type ??\n parsed.questionType ??\n parsed.type ??\n null;\n\n const protocolRaw = String(parsed.protocol ?? \"\").trim();\n const responseCodeRaw = String(parsed.responseCode ?? \"\").trim();\n const responseTypeRaw = String(parsed.responseType ?? \"\").trim();\n\n const normalizedType = String(questionType ?? \"\").trim().toUpperCase();\n const normalizedName = String(questionName ?? \"\").trim();\n\n // Strip optional brackets and port from client IPs (e.g. \"[::1]:5353\" -> \"::1\").\n let normalizedClientIp = String(clientIpRaw ?? \"\").trim();\n const ipMatch = normalizedClientIp.match(/^\\[?([0-9a-fA-F:.]+)\\]?/);\n if (ipMatch) normalizedClientIp = ipMatch[1];\n\n let skipReason = null;\n\n if (!normalizedType) {\n skipReason = \"missing_question_type\";\n } else if (!normalizedName) {\n skipReason = \"missing_question_name\";\n } else if (!normalizedClientIp) {\n skipReason = \"missing_client_ip\";\n } else if (blockedTypes.has(normalizedType)) {\n skipReason = `filtered_type_${normalizedType}`;\n } else if (blockedClientIps.has(normalizedClientIp)) {\n skipReason = `filtered_client_${normalizedClientIp}`;\n }\n\n if (skipReason) {\n out.push({\n json: {\n ok: true,\n forwarded: false,\n skipReason,\n original: parsed,\n originalEntry: entry,\n },\n });\n continue;\n }\n\n const rfc3164Timestamp = formatRfc3164(timestampRaw);\n\n const extras = [\n protocolRaw ? `proto=${protocolRaw}` : null,\n responseCodeRaw ? `rcode=${responseCodeRaw}` : null,\n responseTypeRaw ? `rtype=${responseTypeRaw}` : null,\n ].filter(Boolean).join(\" \");\n\n const syslogLine =\n `<14>${rfc3164Timestamp} technitium-dns dnsmasq[1]: ` +\n `query[${normalizedType}] ${normalizedName} from ${normalizedClientIp}` +\n (extras ? ` ${extras}` : \"\");\n\n out.push({\n json: {\n ok: true,\n forwarded: true,\n skipReason: null,\n syslogLine,\n parsed: {\n timestampRaw,\n clientIp: normalizedClientIp,\n questionName: normalizedName,\n questionType: normalizedType,\n protocol: protocolRaw || null,\n responseCode: responseCodeRaw || null,\n responseType: responseTypeRaw || null,\n },\n original: parsed,\n originalEntry: entry,\n },\n });\n}\n\nreturn out;\n" |
104 | 89 | }, |
105 | 90 | "type": "n8n-nodes-base.code", |
106 | 91 | "typeVersion": 2, |
107 | | - "position": [ |
108 | | - -976, |
109 | | - -304 |
110 | | - ], |
| 92 | + "position": [-976, -304], |
111 | 93 | "id": "ef2d3bd2-1818-4ebd-9f7d-580c0a63a82a", |
112 | 94 | "name": "Transform -> RFC3164" |
113 | 95 | } |
114 | 96 | ], |
115 | 97 | "connections": { |
116 | 98 | "Webhook": { |
117 | | - "main": [ |
118 | | - [ |
119 | | - { |
120 | | - "node": "If", |
121 | | - "type": "main", |
122 | | - "index": 0 |
123 | | - } |
124 | | - ] |
125 | | - ] |
| 99 | + "main": [[{ "node": "If", "type": "main", "index": 0 }]] |
126 | 100 | }, |
127 | 101 | "If": { |
128 | 102 | "main": [ |
129 | | - [ |
130 | | - { |
131 | | - "node": "Transform -> RFC3164", |
132 | | - "type": "main", |
133 | | - "index": 0 |
134 | | - } |
135 | | - ], |
136 | | - [ |
137 | | - { |
138 | | - "node": "Respond to Webhook 'forbidden'", |
139 | | - "type": "main", |
140 | | - "index": 0 |
141 | | - } |
142 | | - ] |
143 | | - ] |
144 | | - }, |
145 | | - "Respond to Webhook 'forbidden'": { |
146 | | - "main": [ |
147 | | - [] |
| 103 | + [{ "node": "Transform -> RFC3164", "type": "main", "index": 0 }], |
| 104 | + [{ "node": "Respond to Webhook 'forbidden'", "type": "main", "index": 0 }] |
148 | 105 | ] |
149 | 106 | }, |
| 107 | + "Respond to Webhook 'forbidden'": { "main": [[]] }, |
150 | 108 | "Execute Command": { |
151 | | - "main": [ |
152 | | - [ |
153 | | - { |
154 | | - "node": "Respond to Webhook 'true'", |
155 | | - "type": "main", |
156 | | - "index": 0 |
157 | | - } |
158 | | - ] |
159 | | - ] |
| 109 | + "main": [[{ "node": "Respond to Webhook 'true'", "type": "main", "index": 0 }]] |
160 | 110 | }, |
161 | 111 | "Transform -> RFC3164": { |
162 | | - "main": [ |
163 | | - [ |
164 | | - { |
165 | | - "node": "Execute Command", |
166 | | - "type": "main", |
167 | | - "index": 0 |
168 | | - } |
169 | | - ] |
170 | | - ] |
| 112 | + "main": [[{ "node": "Execute Command", "type": "main", "index": 0 }]] |
171 | 113 | } |
172 | 114 | }, |
173 | 115 | "active": true, |
|
0 commit comments