@watchflow/n8n-nodes-watchflow 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/credentials/WatchflowApi.credentials.ts +46 -0
- package/dist/credentials/WatchflowApi.credentials.js +43 -0
- package/dist/nodes/Watchflow/Watchflow.node.js +160 -0
- package/dist/nodes/Watchflow/modules/ApiHelper.js +23 -0
- package/dist/nodes/Watchflow/modules/Heartbeat.js +44 -0
- package/dist/nodes/Watchflow/watchflow.svg +4 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @watchflow/n8n-nodes-watchflow
|
|
2
|
+
|
|
3
|
+
This is an n8n community node for [Watchflow.io](https://www.watchflow.io/). It allows you to send heartbeats from your n8n workflows to monitor their health and execution time.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
|
|
8
|
+
|
|
9
|
+
## Credentials
|
|
10
|
+
|
|
11
|
+
To use this node, you'll need a Watchflow API Key. You can get one from the [Watchflow Dashboard](https://app.watchflow.io).
|
|
12
|
+
|
|
13
|
+
## Resources & Operations
|
|
14
|
+
|
|
15
|
+
### Heartbeat
|
|
16
|
+
|
|
17
|
+
Monitor your scheduled tasks and cron jobs.
|
|
18
|
+
|
|
19
|
+
#### Operations:
|
|
20
|
+
- **Ping**: Simple heartbeat successful notification. Marks the job as successful.
|
|
21
|
+
- **Start**: Signal the start of a job. Use this at the beginning of a workflow to track execution duration.
|
|
22
|
+
- **Error**: Signal a job failure. Marks the job as failed and allows you to provide an error message.
|
|
23
|
+
|
|
24
|
+
#### Parameters:
|
|
25
|
+
- **Monitor Key**: A unique identifier for your monitor (e.g., `daily-backup`).
|
|
26
|
+
- **Monitor Name**: A human-readable name for your monitor.
|
|
27
|
+
- **Interval**: Expected heartbeat interval (e.g., `5m`, `1h`, `24h`).
|
|
28
|
+
- **Additional Data**: Custom metrics or metadata as a JSON object.
|
|
29
|
+
- **Error Message**: Description of why the job failed (only for Error operation).
|
|
30
|
+
|
|
31
|
+
## Local Development
|
|
32
|
+
|
|
33
|
+
See [LOCAL-DEVELOPMENT.md](./LOCAL-DEVELOPMENT.md) for instructions on how to run and test this node locally using Docker.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IAuthenticateGeneric,
|
|
3
|
+
ICredentialTestRequest,
|
|
4
|
+
ICredentialType,
|
|
5
|
+
INodeProperties,
|
|
6
|
+
} from "n8n-workflow";
|
|
7
|
+
|
|
8
|
+
export class WatchflowApi implements ICredentialType {
|
|
9
|
+
name = "watchflowApi";
|
|
10
|
+
displayName = "Watchflow API";
|
|
11
|
+
documentationUrl = "https://www.watchflow.io/api/";
|
|
12
|
+
properties: INodeProperties[] = [
|
|
13
|
+
{
|
|
14
|
+
displayName: "API Key",
|
|
15
|
+
name: "apiKey",
|
|
16
|
+
type: "string",
|
|
17
|
+
typeOptions: { password: true },
|
|
18
|
+
default: "",
|
|
19
|
+
description: "You can get API Key for Watchflow from https://app.watchflow.io",
|
|
20
|
+
required: true,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
authenticate: IAuthenticateGeneric = {
|
|
25
|
+
type: "generic",
|
|
26
|
+
properties: {
|
|
27
|
+
headers: {
|
|
28
|
+
"x-api-key": "={{$credentials.apiKey}}",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
test: ICredentialTestRequest = {
|
|
34
|
+
request: {
|
|
35
|
+
baseURL: "https://api.watchflow.io",
|
|
36
|
+
url: "/heartbeat/ping",
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
},
|
|
41
|
+
body: {
|
|
42
|
+
key: "n8n-credential-test",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WatchflowApi = void 0;
|
|
4
|
+
class WatchflowApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = "watchflowApi";
|
|
7
|
+
this.displayName = "Watchflow API";
|
|
8
|
+
this.documentationUrl = "https://www.watchflow.io/api/";
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: "API Key",
|
|
12
|
+
name: "apiKey",
|
|
13
|
+
type: "string",
|
|
14
|
+
typeOptions: { password: true },
|
|
15
|
+
default: "",
|
|
16
|
+
description: "You can get API Key for Watchflow from https://app.watchflow.io",
|
|
17
|
+
required: true,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
this.authenticate = {
|
|
21
|
+
type: "generic",
|
|
22
|
+
properties: {
|
|
23
|
+
headers: {
|
|
24
|
+
"x-api-key": "={{$credentials.apiKey}}",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
this.test = {
|
|
29
|
+
request: {
|
|
30
|
+
baseURL: "https://api.watchflow.io",
|
|
31
|
+
url: "/heartbeat/ping",
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
},
|
|
36
|
+
body: {
|
|
37
|
+
key: "n8n-credential-test",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.WatchflowApi = WatchflowApi;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Watchflow = void 0;
|
|
4
|
+
const ApiHelper_1 = require("./modules/ApiHelper");
|
|
5
|
+
const Heartbeat_1 = require("./modules/Heartbeat");
|
|
6
|
+
class Watchflow {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.description = {
|
|
9
|
+
displayName: 'Watchflow',
|
|
10
|
+
name: 'watchflow',
|
|
11
|
+
icon: 'file:watchflow.svg',
|
|
12
|
+
group: ['transform'],
|
|
13
|
+
version: 1,
|
|
14
|
+
description: 'Heartbeat monitoring for your cron jobs and scheduled tasks.',
|
|
15
|
+
defaults: {
|
|
16
|
+
name: 'Watchflow',
|
|
17
|
+
},
|
|
18
|
+
inputs: ["main" /* NodeConnectionType.Main */],
|
|
19
|
+
outputs: ["main" /* NodeConnectionType.Main */],
|
|
20
|
+
credentials: [
|
|
21
|
+
{
|
|
22
|
+
name: 'watchflowApi',
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
properties: [
|
|
27
|
+
{
|
|
28
|
+
displayName: 'Resource',
|
|
29
|
+
name: 'resource',
|
|
30
|
+
type: 'options',
|
|
31
|
+
noDataExpression: true,
|
|
32
|
+
options: [
|
|
33
|
+
{
|
|
34
|
+
name: 'Heartbeat',
|
|
35
|
+
value: 'heartbeat',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
default: 'heartbeat',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
displayName: 'Operation',
|
|
42
|
+
name: 'operation',
|
|
43
|
+
type: 'options',
|
|
44
|
+
noDataExpression: true,
|
|
45
|
+
displayOptions: {
|
|
46
|
+
show: {
|
|
47
|
+
resource: ['heartbeat'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
options: [
|
|
51
|
+
{
|
|
52
|
+
name: 'Ping',
|
|
53
|
+
value: 'ping',
|
|
54
|
+
action: 'Mark job as successful',
|
|
55
|
+
description: 'Simple ping to mark job as successful',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Start',
|
|
59
|
+
value: 'start',
|
|
60
|
+
action: 'Start job tracking',
|
|
61
|
+
description: 'Start job tracking for duration measurement',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'Error',
|
|
65
|
+
value: 'fail',
|
|
66
|
+
action: 'Mark job as failed',
|
|
67
|
+
description: 'Mark job as failed with error message',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
default: 'ping',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
displayName: 'Monitor Key',
|
|
74
|
+
name: 'key',
|
|
75
|
+
type: 'string',
|
|
76
|
+
default: '',
|
|
77
|
+
required: true,
|
|
78
|
+
displayOptions: {
|
|
79
|
+
show: {
|
|
80
|
+
resource: ['heartbeat'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
description: 'Unique identifier for your monitor (e.g., "daily-backup")',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
displayName: 'Monitor Name',
|
|
87
|
+
name: 'name',
|
|
88
|
+
type: 'string',
|
|
89
|
+
default: '',
|
|
90
|
+
displayOptions: {
|
|
91
|
+
show: {
|
|
92
|
+
resource: ['heartbeat'],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
description: 'Human-readable name for your monitor',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
displayName: 'Interval',
|
|
99
|
+
name: 'interval',
|
|
100
|
+
type: 'string',
|
|
101
|
+
default: '24h',
|
|
102
|
+
displayOptions: {
|
|
103
|
+
show: {
|
|
104
|
+
resource: ['heartbeat'],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
description: 'Expected heartbeat interval (e.g., "5m", "1h", "24h"). Default: 24h',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
displayName: 'Error Message',
|
|
111
|
+
name: 'error',
|
|
112
|
+
type: 'string',
|
|
113
|
+
default: '',
|
|
114
|
+
displayOptions: {
|
|
115
|
+
show: {
|
|
116
|
+
resource: ['heartbeat'],
|
|
117
|
+
operation: ['fail'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
description: 'Description of why the job failed',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
displayName: 'Additional Data',
|
|
124
|
+
name: 'data',
|
|
125
|
+
type: 'json',
|
|
126
|
+
default: '{}',
|
|
127
|
+
displayOptions: {
|
|
128
|
+
show: {
|
|
129
|
+
resource: ['heartbeat'],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
description: 'Custom metrics or metadata (JSON object)',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async execute() {
|
|
138
|
+
const items = this.getInputData();
|
|
139
|
+
const returnData = [];
|
|
140
|
+
const apiHelper = new ApiHelper_1.ApiHelper(this);
|
|
141
|
+
for (let i = 0; i < items.length; i++) {
|
|
142
|
+
try {
|
|
143
|
+
const resource = this.getNodeParameter('resource', i);
|
|
144
|
+
if (resource === 'heartbeat') {
|
|
145
|
+
const result = await Heartbeat_1.executeHeartbeat.call(this, apiHelper, i);
|
|
146
|
+
returnData.push(...result);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (this.continueOnFail()) {
|
|
151
|
+
returnData.push({ json: { error: error.message } });
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return [returnData];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.Watchflow = Watchflow;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiHelper = void 0;
|
|
4
|
+
class ApiHelper {
|
|
5
|
+
constructor(executeFunctions) {
|
|
6
|
+
this.executeFunctions = executeFunctions;
|
|
7
|
+
}
|
|
8
|
+
async request(method, url, body, headers = {}, option = {}) {
|
|
9
|
+
const options = {
|
|
10
|
+
url: `https://api.watchflow.io/heartbeat${url}`,
|
|
11
|
+
method,
|
|
12
|
+
body,
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
...headers,
|
|
16
|
+
},
|
|
17
|
+
json: true,
|
|
18
|
+
...option,
|
|
19
|
+
};
|
|
20
|
+
return this.executeFunctions.helpers.httpRequestWithAuthentication.call(this.executeFunctions, 'watchflowApi', options);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.ApiHelper = ApiHelper;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeHeartbeat = executeHeartbeat;
|
|
4
|
+
async function executeHeartbeat(apiHelper, itemIndex) {
|
|
5
|
+
const operation = this.getNodeParameter('operation', itemIndex);
|
|
6
|
+
const key = this.getNodeParameter('key', itemIndex);
|
|
7
|
+
const endpoints = {
|
|
8
|
+
ping: '/ping',
|
|
9
|
+
start: '/ping/start',
|
|
10
|
+
fail: '/ping/fail',
|
|
11
|
+
};
|
|
12
|
+
const endpoint = endpoints[operation] || '/ping';
|
|
13
|
+
const body = {
|
|
14
|
+
key,
|
|
15
|
+
};
|
|
16
|
+
if (operation === 'fail') {
|
|
17
|
+
const error = this.getNodeParameter('error', itemIndex, '');
|
|
18
|
+
if (error) {
|
|
19
|
+
body.error = error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const interval = this.getNodeParameter('interval', itemIndex, '');
|
|
23
|
+
if (interval) {
|
|
24
|
+
body.interval = interval;
|
|
25
|
+
}
|
|
26
|
+
const name = this.getNodeParameter('name', itemIndex, '');
|
|
27
|
+
if (name) {
|
|
28
|
+
body.name = name;
|
|
29
|
+
}
|
|
30
|
+
let data = this.getNodeParameter('data', itemIndex, {});
|
|
31
|
+
if (typeof data === 'string') {
|
|
32
|
+
try {
|
|
33
|
+
data = JSON.parse(data);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
// If it's not valid JSON, leave it as is or handle accordingly
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (data && typeof data === 'object' && Object.keys(data).length > 0) {
|
|
40
|
+
body.data = data;
|
|
41
|
+
}
|
|
42
|
+
const responseData = await apiHelper.request('POST', endpoint, body);
|
|
43
|
+
return this.helpers.returnJsonArray(responseData);
|
|
44
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M512 128C300.25 128 128 300.25 128 512C128 723.75 300.25 896 512 896C723.75 896 896 723.75 896 512C896 300.25 723.75 128 512 128ZM512 832C335.5 832 192 688.5 192 512C192 335.5 335.5 192 512 192C688.5 192 832 335.5 832 512C832 688.5 688.5 832 512 832Z" fill="#2D3748"/>
|
|
3
|
+
<path d="M512 256C370.75 256 256 370.75 256 512C256 653.25 370.75 768 512 768C653.25 768 768 653.25 768 512C768 370.75 653.25 256 512 256ZM608 512L448 608V416L608 512Z" fill="#2D3748"/>
|
|
4
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@watchflow/n8n-nodes-watchflow",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "n8n node for Watchflow.io",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"watchflow",
|
|
7
|
+
"heartbeat",
|
|
8
|
+
"monitoring",
|
|
9
|
+
"n8n-community-node-package"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://www.watchflow.io/",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/watchflow/n8n-nodes-watchflow.git"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Henrik Lippke",
|
|
19
|
+
"email": "h@customjs.io"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"credentials"
|
|
24
|
+
],
|
|
25
|
+
"n8n": {
|
|
26
|
+
"n8nNodesApiVersion": 1,
|
|
27
|
+
"credentials": [
|
|
28
|
+
"dist/credentials/WatchflowApi.credentials.js"
|
|
29
|
+
],
|
|
30
|
+
"nodes": [
|
|
31
|
+
"dist/nodes/Watchflow/Watchflow.node.js"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^16.11.7",
|
|
36
|
+
"gulp": "^4.0.2",
|
|
37
|
+
"typescript": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"n8n-workflow": "*"
|
|
41
|
+
},
|
|
42
|
+
"type": "commonjs",
|
|
43
|
+
"main": "index.js",
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc && gulp build:icons",
|
|
46
|
+
"dev": "tsc --watch",
|
|
47
|
+
"format": "prettier --write ."
|
|
48
|
+
}
|
|
49
|
+
}
|