-
Notifications
You must be signed in to change notification settings - Fork 6.5k
Expand file tree
/
Copy pathn8n Silent Workflow Check.json
More file actions
164 lines (164 loc) · 5.69 KB
/
Copy pathn8n Silent Workflow Check.json
File metadata and controls
164 lines (164 loc) · 5.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
{
"name": "n8n Silent Workflow Check",
"id": "duskwatchSilentCheck",
"nodes": [
{
"parameters": {
"content": "## Silent workflow check\n\nFinds workflows that are **active but have not run** — the failure mode that produces no failed execution and therefore no error workflow alert.\n\n### Setup (2 minutes)\n1. In n8n: **Settings → n8n API → Create an API key**.\n2. On **Get workflows**, create a *Header Auth* credential:\n - Name: `X-N8N-API-KEY`\n - Value: your API key\n Reuse the same credential on **Get executions**.\n3. In **Configuration**, set `baseUrl` to your n8n address (no trailing slash) and `staleHours` to how long silence is still normal.\n4. Attach your own alert node after **Find silent workflows**.\n\n### The catch\nThis workflow lives inside the instance it watches. If that instance is down, restarting, or its\nschedule triggers did not re-register, this check does not run either — and its silence looks\nexactly like everything being fine.\n\nThat is the one failure it cannot cover. Watching from outside is the only thing that does:\nhttps://duskwatch.me/checklist?ref=template",
"height": 620,
"width": 460
},
"id": "8f1c0a10-0000-4000-8000-000000000001",
"name": "Read me",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-460,
-120
]
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 8
}
]
}
},
"id": "8f1c0a10-0000-4000-8000-000000000002",
"name": "Every morning",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
40,
100
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "a1",
"name": "baseUrl",
"value": "https://n8n.example.com",
"type": "string"
},
{
"id": "a2",
"name": "staleHours",
"value": 26,
"type": "number"
}
]
},
"options": {}
},
"id": "8f1c0a10-0000-4000-8000-000000000003",
"name": "Configuration",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
260,
100
]
},
{
"parameters": {
"url": "={{ $json.baseUrl }}/api/v1/workflows?limit=250",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {}
},
"id": "8f1c0a10-0000-4000-8000-000000000004",
"name": "Get workflows",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
480,
100
]
},
{
"parameters": {
"url": "={{ $('Configuration').first().json.baseUrl }}/api/v1/executions?limit=250",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {}
},
"id": "8f1c0a10-0000-4000-8000-000000000005",
"name": "Get executions",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
700,
100
]
},
{
"parameters": {
"jsCode": "const cfg = $('Configuration').first().json;\nconst stunden = Number(cfg.staleHours) || 24;\n\nconst workflows = ($('Get workflows').first().json.data || []).filter((w) => w.active && !w.isArchived);\nconst executions = $('Get executions').first().json.data || [];\n\n// Letzter Lauf je Workflow. Das Fenster reicht nur so weit zurueck wie limit=250 —\n// bei sehr vielen Ausfuehrungen kann ein stiller Workflow dadurch uebersehen werden.\nconst zuletzt = new Map();\nfor (const e of executions) {\n const id = String(e.workflowId);\n const t = new Date(e.startedAt || e.createdAt).getTime();\n if (!Number.isFinite(t)) continue;\n if (!zuletzt.has(id) || t > zuletzt.get(id)) zuletzt.set(id, t);\n}\n\nconst jetzt = Date.now();\nconst grenze = jetzt - stunden * 3600 * 1000;\nconst still = [];\n\nfor (const w of workflows) {\n const t = zuletzt.get(String(w.id));\n if (t === undefined) {\n still.push({ workflow: w.name, workflowId: w.id, lastRun: null, hoursSilent: null, note: 'no execution in the window' });\n } else if (t < grenze) {\n still.push({ workflow: w.name, workflowId: w.id, lastRun: new Date(t).toISOString(), hoursSilent: Math.round((jetzt - t) / 3600000), note: 'active but stalled' });\n }\n}\n\nif (still.length === 0) return [{ json: { silent: 0, activeChecked: workflows.length } }];\nreturn still.map((json) => ({ json }));"
},
"id": "8f1c0a10-0000-4000-8000-000000000006",
"name": "Find silent workflows",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
920,
100
]
}
],
"connections": {
"Every morning": {
"main": [
[
{
"node": "Configuration",
"type": "main",
"index": 0
}
]
]
},
"Configuration": {
"main": [
[
{
"node": "Get workflows",
"type": "main",
"index": 0
}
]
]
},
"Get workflows": {
"main": [
[
{
"node": "Get executions",
"type": "main",
"index": 0
}
]
]
},
"Get executions": {
"main": [
[
{
"node": "Find silent workflows",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"pinData": {}
}