@processlink/node-red-contrib-processlink 1.0.4 → 1.1.1

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 CHANGED
@@ -1,9 +1,3 @@
1
- <p align="center">
2
- <a href="https://processlink.com.au">
3
- <img src="processlink-logo.png" alt="Process Link" width="80">
4
- </a>
5
- </p>
6
-
7
1
  <p align="center">
8
2
  <a href="https://processlink.com.au">
9
3
  <img src="processlink-banner.png" alt="Process Link" height="50">
@@ -34,6 +28,7 @@ Connect your Node-RED flows to the [Process Link](https://processlink.com.au) pl
34
28
  | Node | Description |
35
29
  |------|-------------|
36
30
  | **files upload** | Upload files to Process Link Files API |
31
+ | **system info** | Output system diagnostics (hostname, memory, disk, uptime, etc.) |
37
32
 
38
33
  *More nodes coming soon: mail, downtime logging, notes*
39
34
 
@@ -76,9 +71,12 @@ Then restart Node-RED.
76
71
  ### 3. Connect Your Flow
77
72
 
78
73
  ```
79
- [File In] → [files upload] → [Debug]
74
+ [File In] → [files upload] ─┬─ Output 1 (success) → [Debug]
75
+ └─ Output 2 (error) → [Debug]
80
76
  ```
81
77
 
78
+ ---
79
+
82
80
  ## Node Reference
83
81
 
84
82
  ### Files Upload
@@ -91,6 +89,7 @@ Uploads files to the Process Link Files API.
91
89
  |----------|-------------|
92
90
  | Config | Your Process Link credentials (Site ID + API Key) |
93
91
  | Filename | Default filename (optional, can be set via `msg.filename`) |
92
+ | Prefix with timestamp | Adds `YYYY-MM-DD_HH-MM-SS_` prefix to filename |
94
93
  | Timeout | Request timeout in milliseconds (default: 30000) |
95
94
 
96
95
  #### Inputs
@@ -102,11 +101,12 @@ Uploads files to the Process Link Files API.
102
101
 
103
102
  #### Outputs
104
103
 
105
- | Property | Type | Description |
106
- |----------|------|-------------|
107
- | `msg.payload` | object | API response with `ok`, `file_id`, `created_at` |
108
- | `msg.file_id` | string | The UUID of the uploaded file |
109
- | `msg.statusCode` | number | HTTP status code (201 on success) |
104
+ This node has **two outputs**:
105
+
106
+ | Output | When | Properties |
107
+ |--------|------|------------|
108
+ | **1 - Success** | HTTP 201 | `msg.payload.ok`, `msg.payload.file_id`, `msg.file_id`, `msg.statusCode` |
109
+ | **2 - Error** | API error, network error, timeout | `msg.payload.error`, `msg.statusCode` |
110
110
 
111
111
  #### Status Indicators
112
112
 
@@ -116,9 +116,82 @@ Uploads files to the Process Link Files API.
116
116
  | 🟢 Green | Upload successful |
117
117
  | 🔴 Red | Error occurred |
118
118
 
119
- ## Example Flow (Copy & Import)
119
+ #### Status Codes
120
+
121
+ | Code | Meaning |
122
+ |------|---------|
123
+ | 201 | Success |
124
+ | 400 | Bad request |
125
+ | 401 | Invalid API key |
126
+ | 403 | API access not enabled |
127
+ | 404 | Site not found |
128
+ | 429 | Rate limit exceeded (max 30/min) |
129
+ | 507 | Storage limit exceeded |
130
+
131
+ ---
132
+
133
+ ### System Info
134
+
135
+ Outputs system information for diagnostics and monitoring.
136
+
137
+ #### Configuration
138
+
139
+ | Property | Description |
140
+ |----------|-------------|
141
+ | Send on deploy | When checked (default), outputs system info when the flow is deployed |
142
+
143
+ #### Triggers
144
+
145
+ - **On deploy** (if enabled) - Automatically sends when flow starts
146
+ - **On input** - Any incoming message triggers a fresh reading
147
+
148
+ #### Output
149
+
150
+ `msg.payload` contains:
151
+
152
+ | Property | Description |
153
+ |----------|-------------|
154
+ | `timestamp` | ISO 8601 UTC timestamp |
155
+ | `localTime` | Device local time string |
156
+ | `timezone` | Timezone name (e.g., "Australia/Sydney") |
157
+ | `hostname` | Device hostname |
158
+ | `platform` | "win32", "linux", or "darwin" |
159
+ | `os` | OS name and version |
160
+ | `arch` | CPU architecture |
161
+ | `user` | User running Node-RED |
162
+ | `workingDirectory` | Node-RED working directory |
163
+ | `uptime` | System uptime (`raw`, `breakdown`, `formatted`) |
164
+ | `cpu` | Model, cores, architecture |
165
+ | `memory` | Total, free, used (bytes + formatted), usedPercent |
166
+ | `disk` | Total, free, used (bytes + formatted), usedPercent |
167
+ | `network` | primaryIP, mac, interfaces |
168
+ | `nodeRed` | version, uptime |
169
+ | `nodejs` | version |
170
+ | `processMemory` | rss, heapTotal, heapUsed |
171
+
172
+ #### Uptime/Memory Structure
173
+
174
+ ```json
175
+ {
176
+ "uptime": {
177
+ "raw": 432000,
178
+ "breakdown": { "days": 5, "hours": 0, "minutes": 0, "seconds": 0 },
179
+ "formatted": "5d 0h 0m 0s"
180
+ },
181
+ "memory": {
182
+ "total": { "bytes": 17179869184, "formatted": "16.00 GB" },
183
+ "free": { "bytes": 8589934592, "formatted": "8.00 GB" },
184
+ "used": { "bytes": 8589934592, "formatted": "8.00 GB" },
185
+ "usedPercent": 50
186
+ }
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Example Flow
120
193
 
121
- Copy the JSON below and import it into Node-RED: **Menu → Import → Clipboard**
194
+ Copy the JSON below and import into Node-RED: **Menu → Import → Clipboard**
122
195
 
123
196
  ```json
124
197
  [
@@ -164,21 +237,36 @@ Copy the JSON below and import it into Node-RED: **Menu → Import → Clipboard
164
237
  "apiUrl": "https://files.processlink.com.au/api/upload",
165
238
  "x": 470,
166
239
  "y": 100,
167
- "wires": [["pl-debug"]]
240
+ "wires": [["pl-debug-success"], ["pl-debug-error"]]
168
241
  },
169
242
  {
170
- "id": "pl-debug",
243
+ "id": "pl-debug-success",
171
244
  "type": "debug",
172
245
  "z": "",
173
- "name": "Result",
246
+ "name": "Success",
174
247
  "active": true,
175
248
  "tosidebar": true,
176
249
  "console": false,
177
250
  "tostatus": false,
178
251
  "complete": "true",
179
252
  "targetType": "full",
180
- "x": 650,
181
- "y": 100,
253
+ "x": 680,
254
+ "y": 80,
255
+ "wires": []
256
+ },
257
+ {
258
+ "id": "pl-debug-error",
259
+ "type": "debug",
260
+ "z": "",
261
+ "name": "Error",
262
+ "active": true,
263
+ "tosidebar": true,
264
+ "console": false,
265
+ "tostatus": false,
266
+ "complete": "true",
267
+ "targetType": "full",
268
+ "x": 670,
269
+ "y": 120,
182
270
  "wires": []
183
271
  }
184
272
  ]
@@ -190,43 +278,7 @@ Copy the JSON below and import it into Node-RED: **Menu → Import → Clipboard
190
278
  3. Click **Deploy**
191
279
  4. Click the inject button to upload
192
280
 
193
- ### Dynamic Filename
194
-
195
- Use a Function node to set the filename dynamically:
196
-
197
- ```javascript
198
- msg.filename = "report-" + new Date().toISOString().split('T')[0] + ".csv";
199
- return msg;
200
- ```
201
-
202
- ### Upload with Error Handling
203
-
204
- ```
205
- [File In] → [files upload] → [Switch] → [Debug (success)]
206
- ↘ [Debug (error)]
207
- ```
208
-
209
- Use a Switch node to route based on `msg.statusCode`:
210
- - Route 1: `msg.statusCode == 201` (success)
211
- - Route 2: Otherwise (error)
212
-
213
- ## Error Handling
214
-
215
- | Status Code | Meaning | Solution |
216
- |-------------|---------|----------|
217
- | 201 | Success | File uploaded successfully |
218
- | 400 | Bad Request | Check that payload is a valid file buffer |
219
- | 401 | Unauthorized | Verify your API key is correct |
220
- | 403 | Forbidden | Enable API access in site settings |
221
- | 404 | Not Found | Verify your Site ID is correct |
222
- | 429 | Rate Limited | Max 30 uploads/minute per site |
223
- | 507 | Storage Full | Contact administrator to increase storage |
224
-
225
- ## Rate Limits
226
-
227
- - **30 uploads per minute** per site
228
- - Exceeding the limit returns a 429 status code
229
- - Implement retry logic in your flow for high-volume uploads
281
+ ---
230
282
 
231
283
  ## Security
232
284
 
@@ -246,10 +298,6 @@ Use a Switch node to route based on `msg.statusCode`:
246
298
  - 📧 **Email**: support@processlink.com.au
247
299
  - 🌐 **Website**: [processlink.com.au](https://processlink.com.au)
248
300
 
249
- ## Contributing
250
-
251
- Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) before submitting a pull request.
252
-
253
301
  ## License
254
302
 
255
303
  [MIT](LICENSE)
@@ -11,7 +11,7 @@
11
11
  apiUrl: { value: "https://files.processlink.com.au/api/upload" },
12
12
  },
13
13
  inputs: 1,
14
- outputs: 1,
14
+ outputs: 2,
15
15
  icon: "processlink.png",
16
16
  paletteLabel: "files upload",
17
17
  label: function () {
@@ -21,7 +21,7 @@
21
21
  return this.name ? "node_label_italic" : "";
22
22
  },
23
23
  inputLabels: "file buffer",
24
- outputLabels: "response",
24
+ outputLabels: ["success", "error"],
25
25
  });
26
26
  </script>
27
27
 
@@ -62,68 +62,60 @@
62
62
  <dt>payload <span class="property-type">buffer | string</span></dt>
63
63
  <dd>The file content to upload.</dd>
64
64
  <dt class="optional">filename <span class="property-type">string</span></dt>
65
- <dd>The filename that will appear in Process Link Files. Can be set via <code>msg.filename</code> or configured in the node. Defaults to "file.bin" if not set. Any path is stripped (e.g., <code>/tmp/report.csv</code> becomes <code>report.csv</code>).</dd>
65
+ <dd>Filename for the upload. Can be set via <code>msg.filename</code> or in the node config. Defaults to "file.bin".</dd>
66
66
  </dl>
67
67
 
68
- <h3>Configuration</h3>
68
+ <h3>Outputs</h3>
69
+ <p>This node has two outputs:</p>
70
+ <ol>
71
+ <li><strong>Success</strong> - Upload completed successfully (HTTP 201)</li>
72
+ <li><strong>Error</strong> - Upload failed (network error, API error, or timeout)</li>
73
+ </ol>
74
+
75
+ <h4>Success Output</h4>
69
76
  <dl class="message-properties">
70
- <dt>Filename <span class="property-type">string</span></dt>
71
- <dd>Default filename to use if <code>msg.filename</code> is not set.</dd>
72
- <dt>Prefix with timestamp <span class="property-type">boolean</span></dt>
73
- <dd>When enabled, automatically prefixes the filename with a timestamp in the format <code>YYYY-MM-DD_HH-MM-SS_</code>. For example, <code>report.csv</code> becomes <code>2025-02-05_14-30-45_report.csv</code>.</dd>
74
- <dt>Timeout <span class="property-type">number</span></dt>
75
- <dd>Request timeout in milliseconds. Default is 30000 (30 seconds).</dd>
77
+ <dt>payload.ok <span class="property-type">boolean</span></dt>
78
+ <dd><code>true</code></dd>
79
+ <dt>payload.file_id <span class="property-type">string</span></dt>
80
+ <dd>UUID of the uploaded file.</dd>
81
+ <dt>file_id <span class="property-type">string</span></dt>
82
+ <dd>Same as above (convenience property).</dd>
83
+ <dt>statusCode <span class="property-type">number</span></dt>
84
+ <dd>201</dd>
76
85
  </dl>
77
86
 
78
- <h3>Outputs</h3>
87
+ <h4>Error Output</h4>
79
88
  <dl class="message-properties">
80
- <dt>payload <span class="property-type">object</span></dt>
81
- <dd>The API response containing <code>ok</code>, <code>file_id</code>, and <code>created_at</code>.</dd>
82
- <dt>file_id <span class="property-type">string</span></dt>
83
- <dd>The UUID of the uploaded file (convenience property).</dd>
89
+ <dt>payload.error <span class="property-type">string</span></dt>
90
+ <dd>Error message from the API or network layer.</dd>
84
91
  <dt>statusCode <span class="property-type">number</span></dt>
85
- <dd>The HTTP status code (201 on success).</dd>
92
+ <dd>HTTP status code, or 0 for network/timeout errors.</dd>
86
93
  </dl>
87
94
 
88
- <h3>Details</h3>
89
- <p>
90
- This node uploads files to your Process Link site. The file content should be passed in
91
- <code>msg.payload</code> as a Buffer (from a file-in node) or as a string.
92
- </p>
93
- <p>
94
- The filename can be set in <code>msg.filename</code> or configured in the node properties. If
95
- the filename contains a path, only the basename will be used.
96
- </p>
95
+ <h3>Configuration</h3>
96
+ <dl class="message-properties">
97
+ <dt>Filename <span class="property-type">string</span></dt>
98
+ <dd>Default filename if <code>msg.filename</code> is not set.</dd>
99
+ <dt>Prefix with timestamp <span class="property-type">boolean</span></dt>
100
+ <dd>Adds <code>YYYY-MM-DD_HH-MM-SS_</code> prefix to filename.</dd>
101
+ <dt>Timeout <span class="property-type">number</span></dt>
102
+ <dd>Request timeout in ms. Default: 30000.</dd>
103
+ </dl>
97
104
 
98
105
  <h3>Status Codes</h3>
99
106
  <ul>
100
- <li><strong>201</strong> - Upload successful</li>
101
- <li><strong>400</strong> - Bad request (missing file, invalid site ID)</li>
107
+ <li><strong>201</strong> - Success</li>
108
+ <li><strong>400</strong> - Bad request</li>
102
109
  <li><strong>401</strong> - Invalid API key</li>
103
- <li><strong>403</strong> - API access not enabled for this site</li>
110
+ <li><strong>403</strong> - API access not enabled</li>
104
111
  <li><strong>404</strong> - Site not found</li>
105
- <li><strong>429</strong> - Rate limit exceeded (max 30 uploads/minute)</li>
112
+ <li><strong>429</strong> - Rate limit exceeded</li>
106
113
  <li><strong>507</strong> - Storage limit exceeded</li>
107
114
  </ul>
108
115
 
109
- <h3>Example Flow</h3>
110
- <pre>
111
- [File In] → [Process Link Upload] → [Debug]
112
-
113
- The File In node reads a file and outputs msg.payload (Buffer)
114
- and msg.filename. Connect directly to this node to upload.</pre>
115
-
116
116
  <h3>References</h3>
117
117
  <ul>
118
- <li>
119
- <a href="https://portal.processlink.com.au" target="_blank">Process Link Portal</a> - Manage
120
- your sites and API keys
121
- </li>
122
- <li>
123
- <a href="https://github.com/process-link/node-red-contrib-processlink" target="_blank"
124
- >GitHub</a
125
- >
126
- - Source code and issues
127
- </li>
118
+ <li><a href="https://portal.processlink.com.au" target="_blank">Process Link Portal</a></li>
119
+ <li><a href="https://github.com/process-link/node-red-contrib-processlink" target="_blank">GitHub</a></li>
128
120
  </ul>
129
121
  </script>
@@ -121,25 +121,24 @@ module.exports = function (RED) {
121
121
  msg.headers = res.headers;
122
122
 
123
123
  if (res.statusCode === 201 && parsedResponse.ok) {
124
- // Success
124
+ // Success - send to output 1
125
125
  msg.file_id = parsedResponse.file_id;
126
126
  node.status({
127
127
  fill: "green",
128
128
  shape: "dot",
129
129
  text: `uploaded: ${parsedResponse.file_id?.substring(0, 8)}...`,
130
130
  });
131
- send(msg);
131
+ send([msg, null]);
132
132
  done();
133
133
 
134
134
  // Clear status after 5 seconds
135
135
  setTimeout(() => node.status({}), 5000);
136
136
  } else {
137
- // API error
137
+ // API error - send to output 2
138
138
  const errorMsg = parsedResponse.error || parsedResponse.message || `HTTP ${res.statusCode}`;
139
139
  node.status({ fill: "red", shape: "dot", text: errorMsg });
140
140
 
141
- // Still send the message so users can handle errors in their flow
142
- send(msg);
141
+ send([null, msg]);
143
142
  done();
144
143
 
145
144
  // Clear status after 10 seconds
@@ -152,7 +151,7 @@ module.exports = function (RED) {
152
151
  node.status({ fill: "red", shape: "ring", text: "request failed" });
153
152
  msg.payload = { error: err.message };
154
153
  msg.statusCode = 0;
155
- send(msg);
154
+ send([null, msg]);
156
155
  done(err);
157
156
 
158
157
  setTimeout(() => node.status({}), 10000);
@@ -165,7 +164,7 @@ module.exports = function (RED) {
165
164
  node.status({ fill: "red", shape: "ring", text: "timeout" });
166
165
  msg.payload = { error: "Request timed out" };
167
166
  msg.statusCode = 0;
168
- send(msg);
167
+ send([null, msg]);
169
168
  done(new Error("Request timed out"));
170
169
 
171
170
  setTimeout(() => node.status({}), 10000);
@@ -0,0 +1,101 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType("processlink-system-info", {
3
+ category: "Process Link",
4
+ color: "#f97316",
5
+ defaults: {
6
+ name: { value: "" },
7
+ sendOnDeploy: { value: true },
8
+ },
9
+ inputs: 1,
10
+ outputs: 1,
11
+ icon: "processlink.png",
12
+ paletteLabel: "system info",
13
+ label: function () {
14
+ return this.name || "system info";
15
+ },
16
+ labelStyle: function () {
17
+ return this.name ? "node_label_italic" : "";
18
+ },
19
+ inputLabels: "trigger",
20
+ outputLabels: "system info",
21
+ });
22
+ </script>
23
+
24
+ <script type="text/html" data-template-name="processlink-system-info">
25
+ <div class="form-row">
26
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
27
+ <input type="text" id="node-input-name" placeholder="Name" />
28
+ </div>
29
+ <div class="form-row">
30
+ <label for="node-input-sendOnDeploy">&nbsp;</label>
31
+ <input type="checkbox" id="node-input-sendOnDeploy" style="width: auto; margin-right: 10px;" checked>
32
+ <span>Send system info on deploy</span>
33
+ </div>
34
+ </script>
35
+
36
+ <script type="text/html" data-help-name="processlink-system-info">
37
+ <p>Outputs system information for diagnostics and monitoring.</p>
38
+
39
+ <h3>Triggers</h3>
40
+ <ul>
41
+ <li><strong>On deploy</strong> (if enabled) - Automatically sends when flow starts</li>
42
+ <li><strong>On input</strong> - Any incoming message triggers a fresh reading</li>
43
+ </ul>
44
+
45
+ <h3>Configuration</h3>
46
+ <dl class="message-properties">
47
+ <dt>Send on deploy <span class="property-type">boolean</span></dt>
48
+ <dd>When checked (default), outputs system info when the flow is deployed.</dd>
49
+ </dl>
50
+
51
+ <h3>Output</h3>
52
+ <p><code>msg.payload</code> contains:</p>
53
+
54
+ <table>
55
+ <tr><td><strong>timestamp</strong></td><td>ISO 8601 UTC timestamp</td></tr>
56
+ <tr><td><strong>localTime</strong></td><td>Device local time string</td></tr>
57
+ <tr><td><strong>timezone</strong></td><td>Timezone name (e.g., "Australia/Sydney")</td></tr>
58
+ <tr><td><strong>hostname</strong></td><td>Device hostname</td></tr>
59
+ <tr><td><strong>platform</strong></td><td>"win32", "linux", or "darwin"</td></tr>
60
+ <tr><td><strong>os</strong></td><td>OS name and version</td></tr>
61
+ <tr><td><strong>arch</strong></td><td>CPU architecture</td></tr>
62
+ <tr><td><strong>user</strong></td><td>User running Node-RED</td></tr>
63
+ <tr><td><strong>workingDirectory</strong></td><td>Node-RED working directory</td></tr>
64
+ <tr><td><strong>uptime</strong></td><td>System uptime (raw seconds, breakdown, formatted)</td></tr>
65
+ <tr><td><strong>cpu</strong></td><td>Model, cores, architecture</td></tr>
66
+ <tr><td><strong>memory</strong></td><td>Total, free, used (bytes + formatted), usedPercent</td></tr>
67
+ <tr><td><strong>disk</strong></td><td>Total, free, used (bytes + formatted), usedPercent</td></tr>
68
+ <tr><td><strong>network</strong></td><td>primaryIP, mac, interfaces</td></tr>
69
+ <tr><td><strong>nodeRed</strong></td><td>version, uptime</td></tr>
70
+ <tr><td><strong>nodejs</strong></td><td>version</td></tr>
71
+ <tr><td><strong>processMemory</strong></td><td>rss, heapTotal, heapUsed</td></tr>
72
+ </table>
73
+
74
+ <h3>Uptime Structure</h3>
75
+ <pre>{
76
+ "raw": 432000,
77
+ "breakdown": { "days": 5, "hours": 0, "minutes": 0, "seconds": 0 },
78
+ "formatted": "5d 0h 0m 0s"
79
+ }</pre>
80
+
81
+ <h3>Memory/Disk Structure</h3>
82
+ <pre>{
83
+ "total": { "bytes": 17179869184, "formatted": "16.00 GB" },
84
+ "free": { "bytes": 8589934592, "formatted": "8.00 GB" },
85
+ "used": { "bytes": 8589934592, "formatted": "8.00 GB" },
86
+ "usedPercent": 50
87
+ }</pre>
88
+
89
+ <h3>Notes</h3>
90
+ <ul>
91
+ <li><strong>disk</strong> may be unavailable on some systems due to permissions.</li>
92
+ <li><strong>nodeRed.uptime</strong> tracks time since flow was deployed.</li>
93
+ <li>Use <strong>usedPercent</strong> for easy threshold checks.</li>
94
+ </ul>
95
+
96
+ <h3>References</h3>
97
+ <ul>
98
+ <li><a href="https://portal.processlink.com.au" target="_blank">Process Link Portal</a></li>
99
+ <li><a href="https://github.com/process-link/node-red-contrib-processlink" target="_blank">GitHub</a></li>
100
+ </ul>
101
+ </script>
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Process Link System Info Node
3
+ * Outputs system information for diagnostics and monitoring
4
+ */
5
+
6
+ module.exports = function (RED) {
7
+ const os = require("os");
8
+ const { execSync } = require("child_process");
9
+
10
+ /**
11
+ * Format bytes to human-readable string
12
+ * @param {number} bytes
13
+ * @returns {string}
14
+ */
15
+ function formatBytes(bytes) {
16
+ if (bytes === 0) return "0 B";
17
+ const units = ["B", "KB", "MB", "GB", "TB"];
18
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
19
+ return (bytes / Math.pow(1024, i)).toFixed(2) + " " + units[i];
20
+ }
21
+
22
+ /**
23
+ * Convert seconds to broken-down time object
24
+ * @param {number} totalSeconds
25
+ * @returns {object}
26
+ */
27
+ function formatUptime(totalSeconds) {
28
+ const days = Math.floor(totalSeconds / 86400);
29
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
30
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
31
+ const seconds = Math.floor(totalSeconds % 60);
32
+
33
+ return {
34
+ raw: Math.floor(totalSeconds),
35
+ breakdown: {
36
+ days: days,
37
+ hours: hours,
38
+ minutes: minutes,
39
+ seconds: seconds,
40
+ },
41
+ formatted: `${days}d ${hours}h ${minutes}m ${seconds}s`,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Get primary network interface info (first non-internal IPv4)
47
+ * @returns {object}
48
+ */
49
+ function getNetworkInfo() {
50
+ const interfaces = os.networkInterfaces();
51
+ let primaryIP = null;
52
+ let primaryMAC = null;
53
+
54
+ // Find first non-internal IPv4 address
55
+ for (const name of Object.keys(interfaces)) {
56
+ for (const iface of interfaces[name]) {
57
+ if (iface.family === "IPv4" && !iface.internal) {
58
+ if (!primaryIP) {
59
+ primaryIP = iface.address;
60
+ primaryMAC = iface.mac;
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ return {
67
+ interfaces: interfaces,
68
+ primaryIP: primaryIP || "unknown",
69
+ mac: primaryMAC || "unknown",
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Get disk space information (cross-platform)
75
+ * @returns {object|null}
76
+ */
77
+ function getDiskInfo() {
78
+ try {
79
+ const platform = os.platform();
80
+ let total, free, used;
81
+
82
+ if (platform === "win32") {
83
+ // Windows: use wmic
84
+ const output = execSync("wmic logicaldisk where drivetype=3 get size,freespace", {
85
+ encoding: "utf8",
86
+ timeout: 5000,
87
+ });
88
+ const lines = output.trim().split("\n").filter((l) => l.trim());
89
+ if (lines.length > 1) {
90
+ // Sum all drives
91
+ total = 0;
92
+ free = 0;
93
+ for (let i = 1; i < lines.length; i++) {
94
+ const parts = lines[i].trim().split(/\s+/);
95
+ if (parts.length >= 2) {
96
+ free += parseInt(parts[0]) || 0;
97
+ total += parseInt(parts[1]) || 0;
98
+ }
99
+ }
100
+ used = total - free;
101
+ }
102
+ } else {
103
+ // Linux/Mac: use df
104
+ const output = execSync("df -B1 / | tail -1", {
105
+ encoding: "utf8",
106
+ timeout: 5000,
107
+ });
108
+ const parts = output.trim().split(/\s+/);
109
+ if (parts.length >= 4) {
110
+ total = parseInt(parts[1]) || 0;
111
+ used = parseInt(parts[2]) || 0;
112
+ free = parseInt(parts[3]) || 0;
113
+ }
114
+ }
115
+
116
+ if (total && total > 0) {
117
+ return {
118
+ total: {
119
+ bytes: total,
120
+ formatted: formatBytes(total),
121
+ },
122
+ free: {
123
+ bytes: free,
124
+ formatted: formatBytes(free),
125
+ },
126
+ used: {
127
+ bytes: used,
128
+ formatted: formatBytes(used),
129
+ },
130
+ usedPercent: Math.round((used / total) * 100),
131
+ };
132
+ }
133
+ } catch (e) {
134
+ // Disk info unavailable
135
+ }
136
+ return null;
137
+ }
138
+
139
+ /**
140
+ * Get Node-RED version
141
+ * @returns {string}
142
+ */
143
+ function getNodeRedVersion() {
144
+ try {
145
+ return RED.version();
146
+ } catch (e) {
147
+ return "unknown";
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Collect all system information
153
+ * @param {number} nodeRedStartTime - timestamp when Node-RED started
154
+ * @returns {object}
155
+ */
156
+ function collectSystemInfo(nodeRedStartTime) {
157
+ const now = new Date();
158
+ const totalMem = os.totalmem();
159
+ const freeMem = os.freemem();
160
+ const usedMem = totalMem - freeMem;
161
+ const cpus = os.cpus();
162
+ const processMemory = process.memoryUsage();
163
+ const networkInfo = getNetworkInfo();
164
+ const diskInfo = getDiskInfo();
165
+
166
+ // Calculate Node-RED uptime
167
+ const nodeRedUptimeSeconds = (Date.now() - nodeRedStartTime) / 1000;
168
+
169
+ const info = {
170
+ timestamp: now.toISOString(),
171
+ localTime: now.toLocaleString(),
172
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
173
+
174
+ hostname: os.hostname(),
175
+ platform: os.platform(),
176
+ os: `${os.type()} ${os.release()}`,
177
+ arch: os.arch(),
178
+ user: os.userInfo().username,
179
+ workingDirectory: process.cwd(),
180
+
181
+ uptime: formatUptime(os.uptime()),
182
+
183
+ cpu: {
184
+ model: cpus.length > 0 ? cpus[0].model : "unknown",
185
+ cores: cpus.length,
186
+ arch: os.arch(),
187
+ },
188
+
189
+ memory: {
190
+ total: {
191
+ bytes: totalMem,
192
+ formatted: formatBytes(totalMem),
193
+ },
194
+ free: {
195
+ bytes: freeMem,
196
+ formatted: formatBytes(freeMem),
197
+ },
198
+ used: {
199
+ bytes: usedMem,
200
+ formatted: formatBytes(usedMem),
201
+ },
202
+ usedPercent: Math.round((usedMem / totalMem) * 100),
203
+ },
204
+
205
+ network: networkInfo,
206
+
207
+ nodeRed: {
208
+ version: getNodeRedVersion(),
209
+ uptime: formatUptime(nodeRedUptimeSeconds),
210
+ },
211
+
212
+ nodejs: {
213
+ version: process.version,
214
+ },
215
+
216
+ processMemory: {
217
+ rss: {
218
+ bytes: processMemory.rss,
219
+ formatted: formatBytes(processMemory.rss),
220
+ },
221
+ heapTotal: {
222
+ bytes: processMemory.heapTotal,
223
+ formatted: formatBytes(processMemory.heapTotal),
224
+ },
225
+ heapUsed: {
226
+ bytes: processMemory.heapUsed,
227
+ formatted: formatBytes(processMemory.heapUsed),
228
+ },
229
+ },
230
+ };
231
+
232
+ // Add disk info if available
233
+ if (diskInfo) {
234
+ info.disk = diskInfo;
235
+ }
236
+
237
+ return info;
238
+ }
239
+
240
+ function ProcessLinkSystemInfoNode(config) {
241
+ RED.nodes.createNode(this, config);
242
+ const node = this;
243
+
244
+ // Record when this node was created (proxy for Node-RED start time)
245
+ const nodeRedStartTime = Date.now();
246
+
247
+ // Send on deploy if enabled
248
+ if (config.sendOnDeploy) {
249
+ // Small delay to let Node-RED fully initialize
250
+ setTimeout(() => {
251
+ const info = collectSystemInfo(nodeRedStartTime);
252
+ node.send({ payload: info });
253
+ node.status({
254
+ fill: "green",
255
+ shape: "dot",
256
+ text: `sent @ ${new Date().toLocaleTimeString()}`,
257
+ });
258
+ setTimeout(() => node.status({}), 5000);
259
+ }, 1000);
260
+ }
261
+
262
+ node.on("input", function (msg, send, done) {
263
+ // For Node-RED 0.x compatibility
264
+ send = send || function () { node.send.apply(node, arguments); };
265
+ done = done || function (err) { if (err) node.error(err, msg); };
266
+
267
+ try {
268
+ node.status({ fill: "yellow", shape: "dot", text: "collecting..." });
269
+
270
+ const info = collectSystemInfo(nodeRedStartTime);
271
+ msg.payload = info;
272
+
273
+ node.status({
274
+ fill: "green",
275
+ shape: "dot",
276
+ text: `sent @ ${new Date().toLocaleTimeString()}`,
277
+ });
278
+
279
+ send(msg);
280
+ done();
281
+
282
+ // Clear status after 5 seconds
283
+ setTimeout(() => node.status({}), 5000);
284
+ } catch (err) {
285
+ node.status({ fill: "red", shape: "ring", text: "error" });
286
+ done(err);
287
+ setTimeout(() => node.status({}), 10000);
288
+ }
289
+ });
290
+
291
+ node.on("close", function () {
292
+ node.status({});
293
+ });
294
+ }
295
+
296
+ RED.nodes.registerType("processlink-system-info", ProcessLinkSystemInfoNode);
297
+ };
package/package.json CHANGED
@@ -1,54 +1,58 @@
1
- {
2
- "name": "@processlink/node-red-contrib-processlink",
3
- "publishConfig": {
4
- "access": "public"
5
- },
6
- "version": "1.0.4",
7
- "description": "Node-RED nodes for Process Link platform integration - upload files, send notifications, and connect to industrial automation systems",
8
- "keywords": [
9
- "node-red",
10
- "processlink",
11
- "process-link",
12
- "files",
13
- "upload",
14
- "api",
15
- "industrial",
16
- "iot",
17
- "automation",
18
- "manufacturing",
19
- "scada",
20
- "plc"
21
- ],
22
- "node-red": {
23
- "version": ">=2.0.0",
24
- "nodes": {
25
- "processlink-config": "nodes/config/processlink-config.js",
26
- "processlink-files-upload": "nodes/files/processlink-files-upload.js"
27
- }
28
- },
29
- "scripts": {
30
- "test": "echo \"No tests yet\" && exit 0"
31
- },
32
- "author": {
33
- "name": "Process Link",
34
- "email": "support@processlink.com.au",
35
- "url": "https://processlink.com.au"
36
- },
37
- "contributors": [],
38
- "license": "MIT",
39
- "repository": {
40
- "type": "git",
41
- "url": "git+https://github.com/process-link/node-red-contrib-processlink.git"
42
- },
43
- "bugs": {
44
- "url": "https://github.com/process-link/node-red-contrib-processlink/issues"
45
- },
46
- "homepage": "https://processlink.com.au",
47
- "funding": {
48
- "type": "individual",
49
- "url": "https://processlink.com.au"
50
- },
51
- "engines": {
52
- "node": ">=14.0.0"
53
- }
54
- }
1
+ {
2
+ "name": "@processlink/node-red-contrib-processlink",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "1.1.1",
7
+ "description": "Node-RED nodes for Process Link platform integration - upload files, send notifications, and connect to industrial automation systems",
8
+ "keywords": [
9
+ "node-red",
10
+ "processlink",
11
+ "process-link",
12
+ "files",
13
+ "upload",
14
+ "api",
15
+ "industrial",
16
+ "iot",
17
+ "automation",
18
+ "manufacturing",
19
+ "scada",
20
+ "plc",
21
+ "system-info",
22
+ "diagnostics",
23
+ "monitoring"
24
+ ],
25
+ "node-red": {
26
+ "version": ">=2.0.0",
27
+ "nodes": {
28
+ "processlink-config": "nodes/config/processlink-config.js",
29
+ "processlink-files-upload": "nodes/files/processlink-files-upload.js",
30
+ "processlink-system-info": "nodes/system/processlink-system-info.js"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "test": "echo \"No tests yet\" && exit 0"
35
+ },
36
+ "author": {
37
+ "name": "Process Link",
38
+ "email": "support@processlink.com.au",
39
+ "url": "https://processlink.com.au"
40
+ },
41
+ "contributors": [],
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/process-link/node-red-contrib-processlink.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/process-link/node-red-contrib-processlink/issues"
49
+ },
50
+ "homepage": "https://processlink.com.au",
51
+ "funding": {
52
+ "type": "individual",
53
+ "url": "https://processlink.com.au"
54
+ },
55
+ "engines": {
56
+ "node": ">=14.0.0"
57
+ }
58
+ }