@processlink/node-red-contrib-processlink 1.0.4 → 1.1.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
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">
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
apiUrl: { value: "https://files.processlink.com.au/api/upload" },
|
|
12
12
|
},
|
|
13
13
|
inputs: 1,
|
|
14
|
-
outputs:
|
|
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: "
|
|
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>
|
|
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>
|
|
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>
|
|
71
|
-
<dd
|
|
72
|
-
<dt>
|
|
73
|
-
<dd>
|
|
74
|
-
<dt>
|
|
75
|
-
<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
|
-
<
|
|
87
|
+
<h4>Error Output</h4>
|
|
79
88
|
<dl class="message-properties">
|
|
80
|
-
<dt>payload <span class="property-type">
|
|
81
|
-
<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>
|
|
92
|
+
<dd>HTTP status code, or 0 for network/timeout errors.</dd>
|
|
86
93
|
</dl>
|
|
87
94
|
|
|
88
|
-
<h3>
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
<code>msg.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
</
|
|
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> -
|
|
101
|
-
<li><strong>400</strong> - Bad request
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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"> </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
|
|
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
|
-
|
|
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
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@processlink/node-red-contrib-processlink",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "1.1.0",
|
|
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
|
+
}
|