node-red-contrib-uos-nats 1.4.0 → 1.4.6

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
@@ -109,6 +109,33 @@ Publishes your own data to the Data Hub.
109
109
 
110
110
  ---
111
111
 
112
+ ### 3. u-OS API Node (Orange)
113
+ The **u-OS API** node provides a unified HTTP interface for both DataHub Variables and System Administration.
114
+
115
+ * **Mode: DataHub Variables**
116
+ * Alternative access to variables via HTTP (REST) instead of NATS.
117
+ * Actions: `Read`, `Write`, `List Providers`, `List Variables`.
118
+ * Useful for one-off requests or environments where NATS is restricted.
119
+
120
+ * **Mode: System Administration**
121
+ * Manage the u-OS device itself.
122
+ * **Categories**:
123
+ * **System**: Get info, nameplate, disk usage, or trigger reboot.
124
+ * **Network**: Read/Write network IP/Gateway/DNS configuration.
125
+ * **Security**: Enable/Disable Root access.
126
+ * **Logging**: Access system logs and trigger reports.
127
+ * **Recovery**: Factory Reset.
128
+ * **Requirements**: You must enable **System Admin Access** in the `u-OS Config` node to grant the necessary OAuth scopes (`u-os-adm.*`).
129
+
130
+ ## Configuration
131
+ 1. **Host**: IP address of the u-OS device (default: `127.0.0.1` for local Node-RED).
132
+ 2. **Auth**: Client ID and Client Secret (from u-OS Device Administration).
133
+ 3. **Scopes**:
134
+ * Standard scopes for DataHub are active by default.
135
+ * Check **"Enable System Admin Access"** to manage the system.
136
+
137
+ ---
138
+
112
139
  ## Performance & Reliability
113
140
  - **High-Speed Decoding (Filter-on-Decode):** The node now intelligently filters incoming data at the byte-level. Even if a provider sends thousands of variables, Node-RED only decodes the ones you have selected. This massively reduces CPU usage.
114
141
  - **Large Buffers:** The internal NATS buffer has been increased (10MB) to handle event bursts (e.g. rapid switching).
@@ -0,0 +1,150 @@
1
+ [
2
+ {
3
+ "id": "intro-tab",
4
+ "type": "tab",
5
+ "label": "u-OS API Demo",
6
+ "disabled": false,
7
+ "info": "Example flow for the **u-OS API** node.\n\nDemonstrates:\n1. **System Admin**: Getting System Info (CPU, OS Model)\n2. **DataHub**: Reading a variable via HTTP (instead of NATS)\n\n**Setup:**\n- Open the `u-OS API` nodes and configure your `u-OS Config` credentials.\n- Determine a valid Provider/Variable for the DataHub test."
8
+ },
9
+ {
10
+ "id": "inject-sys-info",
11
+ "type": "inject",
12
+ "z": "intro-tab",
13
+ "name": "Get System Info",
14
+ "props": [
15
+ {
16
+ "p": "payload"
17
+ },
18
+ {
19
+ "p": "topic",
20
+ "vt": "str"
21
+ }
22
+ ],
23
+ "repeat": "",
24
+ "crontab": "",
25
+ "once": false,
26
+ "onceDelay": 0.1,
27
+ "topic": "",
28
+ "payload": "",
29
+ "payloadType": "date",
30
+ "x": 160,
31
+ "y": 120,
32
+ "wires": [
33
+ [
34
+ "uos-api-sys-info"
35
+ ]
36
+ ]
37
+ },
38
+ {
39
+ "id": "uos-api-sys-info",
40
+ "type": "uos-api",
41
+ "z": "intro-tab",
42
+ "name": "System Info",
43
+ "uosConfig": "config-node-id",
44
+ "mode": "system",
45
+ "dhAction": "read",
46
+ "dhProvider": "",
47
+ "dhVariable": "",
48
+ "sysCategory": "system",
49
+ "sysAction": "info",
50
+ "x": 370,
51
+ "y": 120,
52
+ "wires": [
53
+ [
54
+ "debug-sys-info"
55
+ ]
56
+ ]
57
+ },
58
+ {
59
+ "id": "debug-sys-info",
60
+ "type": "debug",
61
+ "z": "intro-tab",
62
+ "name": "System Info Output",
63
+ "active": true,
64
+ "tosidebar": true,
65
+ "console": false,
66
+ "tostatus": false,
67
+ "complete": "payload",
68
+ "targetType": "msg",
69
+ "statusVal": "",
70
+ "statusType": "auto",
71
+ "x": 590,
72
+ "y": 120,
73
+ "wires": []
74
+ },
75
+ {
76
+ "id": "inject-dh-read",
77
+ "type": "inject",
78
+ "z": "intro-tab",
79
+ "name": "Read Variable (HTTP)",
80
+ "props": [
81
+ {
82
+ "p": "payload"
83
+ },
84
+ {
85
+ "p": "topic",
86
+ "vt": "str"
87
+ }
88
+ ],
89
+ "repeat": "",
90
+ "crontab": "",
91
+ "once": false,
92
+ "onceDelay": 0.1,
93
+ "topic": "",
94
+ "payload": "",
95
+ "payloadType": "date",
96
+ "x": 180,
97
+ "y": 220,
98
+ "wires": [
99
+ [
100
+ "uos-api-dh-read"
101
+ ]
102
+ ]
103
+ },
104
+ {
105
+ "id": "uos-api-dh-read",
106
+ "type": "uos-api",
107
+ "z": "intro-tab",
108
+ "name": "Read via HTTP",
109
+ "uosConfig": "config-node-id",
110
+ "mode": "datahub",
111
+ "dhAction": "read",
112
+ "dhProvider": "u_os_adm",
113
+ "dhVariable": "digital_nameplate.serial_number",
114
+ "sysCategory": "system",
115
+ "sysAction": "info",
116
+ "x": 380,
117
+ "y": 220,
118
+ "wires": [
119
+ [
120
+ "debug-dh-read"
121
+ ]
122
+ ]
123
+ },
124
+ {
125
+ "id": "debug-dh-read",
126
+ "type": "debug",
127
+ "z": "intro-tab",
128
+ "name": "Variable Value",
129
+ "active": true,
130
+ "tosidebar": true,
131
+ "console": false,
132
+ "tostatus": false,
133
+ "complete": "payload",
134
+ "targetType": "msg",
135
+ "statusVal": "",
136
+ "statusType": "auto",
137
+ "x": 580,
138
+ "y": 220,
139
+ "wires": []
140
+ },
141
+ {
142
+ "id": "config-node-id",
143
+ "type": "uos-config",
144
+ "name": "Local u-OS",
145
+ "host": "127.0.0.1",
146
+ "port": 4222,
147
+ "clientName": "nodered",
148
+ "enableSystemAdmin": true
149
+ }
150
+ ]
@@ -1,4 +1,4 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="60" height="60">
2
- <rect width="60" height="60" rx="6" ry="6" fill="#a6bbcf" />
3
- <text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="24" fill="white">API</text>
2
+ <rect width="60" height="60" rx="6" ry="6" fill="#FFCC66" />
3
+ <text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="24" fill="#333">API</text>
4
4
  </svg>
@@ -0,0 +1,492 @@
1
+ RED.nodes.registerType('uos-api', {
2
+ category: 'Weidmueller DataHub',
3
+ color: '#FFCC66',
4
+ defaults: {
5
+ name: { value: "" },
6
+ uosConfig: { type: "uos-config", required: true },
7
+ mode: { value: "datahub" },
8
+
9
+ // DataHub
10
+ dhAction: { value: "read" },
11
+ dhProvider: { value: "" },
12
+ dhVariable: { value: "" },
13
+
14
+ // System
15
+ sysCategory: { value: "system" },
16
+ sysAction: { value: "info" }
17
+ },
18
+ inputs: 1,
19
+ outputs: 1,
20
+ icon: "uos-api.svg",
21
+ label: function () {
22
+ return this.name || "u-OS API";
23
+ },
24
+ oneditprepare: function () {
25
+ $("#node-input-mode").on("change", function () {
26
+ var mode = $(this).val();
27
+ if (mode === "datahub") {
28
+ $(".uos-mode-datahub").show();
29
+ $(".uos-mode-system").hide();
30
+ $("#node-input-dhAction").change(); // Trigger redraw of vars
31
+ } else {
32
+ $(".uos-mode-datahub").hide();
33
+ $(".uos-mode-system").show();
34
+ }
35
+ });
36
+
37
+ // DataHub Action UI refinements
38
+ $("#node-input-dhAction").on("change", function () {
39
+ var action = $(this).val();
40
+ if (action === "list_providers") {
41
+ $("#node-input-dhProvider").parent().hide();
42
+ $("#node-input-dhVariable").parent().hide();
43
+ } else if (action === "list_variables") {
44
+ $("#node-input-dhProvider").parent().show();
45
+ $("#node-input-dhVariable").parent().hide();
46
+ } else {
47
+ $("#node-input-dhProvider").parent().show();
48
+ $("#node-input-dhVariable").parent().show();
49
+ }
50
+ });
51
+
52
+ // System Action Dynamic Options
53
+ const sysActions = {
54
+ system: [
55
+ { val: "info", label: "Get System Info (Model, CPU, Version)" },
56
+ { val: "nameplate", label: "Get Digital Nameplate (Serial, etc.)" },
57
+ { val: "disks", label: "Get Disk Usage" },
58
+ { val: "reboot", label: "Reboot System [POST]" }
59
+ ],
60
+ network: [
61
+ { val: "get_state", label: "Get Network State (IPs)" },
62
+ { val: "get_config", label: "Get Network Config (Settings)" },
63
+ { val: "set_config", label: "Set Network Config (PUT)" },
64
+ { val: "update_config", label: "Update Network Config (PATCH)" }
65
+ ],
66
+ logging: [
67
+ { val: "entries", label: "Get Log Entries" },
68
+ { val: "sources", label: "Get Log Sources" },
69
+ { val: "create_report", label: "Create Log Report [POST]" }
70
+ ],
71
+ security: [
72
+ { val: "get_config", label: "Get Security Config (Root Access)" },
73
+ { val: "set_config", label: "Set Security Config (PUT)" }
74
+ ],
75
+ recovery: [
76
+ { val: "factory_reset", label: "Factory Reset [POST]" }
77
+ ]
78
+ };
79
+
80
+ $("#node-input-sysCategory").on("change", function () {
81
+ var cat = $(this).val();
82
+ var actions = sysActions[cat] || [];
83
+ var select = $("#node-input-sysAction");
84
+ select.empty();
85
+ actions.forEach(function (a) {
86
+ select.append($("<option></option>").attr("value", a.val).text(a.label));
87
+ });
88
+
89
+ // If the current sysAction value is not valid for this category, reset it
90
+ const currentVal = $("#node-input-sysAction").val();
91
+ let found = false;
92
+ actions.forEach(action => { if (action.val === currentVal) found = true; });
93
+
94
+ if (!found && actions.length > 0) {
95
+ $("#node-input-sysAction").val(actions[0].val);
96
+ } else if (found) {
97
+ $("#node-input-sysAction").val(currentVal);
98
+ }
99
+ });
100
+
101
+ // Provider Browsing
102
+ $("#btn-fetch-providers").click(function() {
103
+ var configId = $("#node-input-uosConfig").val();
104
+ if (!configId || configId == "_ADD_") {
105
+ RED.notify("Please select a u-OS Config first.", "error");
106
+ return;
107
+ }
108
+
109
+ var $btn = $(this); $btn.prop('disabled', true);
110
+
111
+ $.getJSON('uos-api/providers/' + configId, function(data) {
112
+ $btn.prop('disabled', false);
113
+ if (!Array.isArray(data)) { RED.notify("Invalid response", "error"); return; }
114
+
115
+ // Simple replacement with autocomplete or just a quick select list could be better
116
+ // For now, let's just show a notification list or simplistic approach
117
+ // Ideally we would replace the input with a select, but we want to allow custom text.
118
+ // Let's use a jQuery UI dialog or simple prompt-like behavior?
119
+ // Let's replace the Input with a Select momentarily to let user pick?
120
+
121
+ var providers = data.map(p => p.id);
122
+ // Use TypeInput's autocomplete? Or just standard array?
123
+ $("#node-input-dhProvider").autocomplete({
124
+ source: providers,
125
+ minLength: 0
126
+ }).autocomplete("search", "");
127
+
128
+ RED.notify("Found " + providers.length + " providers. Start typing to see suggestions.", "success");
129
+
130
+ }).fail(function(jqxhr) {
131
+ $btn.prop('disabled', false);
132
+ RED.notify("Failed to load providers: " + (jqxhr.responseJSON?.error || "Unknown"), "error");
133
+ });
134
+ });
135
+
136
+ category: 'Weidmueller DataHub',
137
+ color: '#FFCC66',
138
+ defaults: {
139
+ name: { value: "" },
140
+ uosConfig: { type: "uos-config", required: true },
141
+ mode: { value: "datahub" },
142
+
143
+ // DataHub
144
+ dhAction: { value: "read" },
145
+ dhProvider: { value: "" },
146
+ dhVariable: { value: "" },
147
+
148
+ // System
149
+ sysCategory: { value: "system" },
150
+ sysAction: { value: "info" }
151
+ },
152
+ inputs: 1,
153
+ outputs: 1,
154
+ icon: "uos-api.svg",
155
+ label: function () {
156
+ return this.name || "u-OS API";
157
+ },
158
+ oneditprepare: function () {
159
+ $("#node-input-mode").on("change", function () {
160
+ var mode = $(this).val();
161
+ if (mode === "datahub") {
162
+ $(".uos-mode-datahub").show();
163
+ $(".uos-mode-system").hide();
164
+ $("#node-input-dhAction").change(); // Trigger redraw of vars
165
+ } else {
166
+ $(".uos-mode-datahub").hide();
167
+ $(".uos-mode-system").show();
168
+ }
169
+ });
170
+
171
+ // DataHub Action UI refinements
172
+ $("#node-input-dhAction").on("change", function () {
173
+ var action = $(this).val();
174
+ if (action === "list_providers") {
175
+ $("#node-input-dhProvider").parent().hide();
176
+ $("#node-input-dhVariable").parent().hide();
177
+ } else if (action === "list_variables") {
178
+ $("#node-input-dhProvider").parent().show();
179
+ $("#node-input-dhVariable").parent().hide();
180
+ } else {
181
+ $("#node-input-dhProvider").parent().show();
182
+ $("#node-input-dhVariable").parent().show();
183
+ }
184
+ });
185
+
186
+ // System Action Dynamic Options
187
+ const sysActions = {
188
+ system: [
189
+ { val: "info", label: "Get System Info (Model, CPU, Version)" },
190
+ { val: "nameplate", label: "Get Digital Nameplate (Serial, etc.)" },
191
+ { val: "disks", label: "Get Disk Usage" },
192
+ { val: "reboot", label: "Reboot System [POST]" }
193
+ ],
194
+ network: [
195
+ { val: "get_state", label: "Get Network State (IPs)" },
196
+ { val: "get_config", label: "Get Network Config (Settings)" },
197
+ { val: "set_config", label: "Set Network Config (PUT)" },
198
+ { val: "update_config", label: "Update Network Config (PATCH)" }
199
+ ],
200
+ logging: [
201
+ { val: "entries", label: "Get Log Entries" },
202
+ { val: "sources", label: "Get Log Sources" },
203
+ { val: "create_report", label: "Create Log Report [POST]" }
204
+ ],
205
+ security: [
206
+ { val: "get_config", label: "Get Security Config (Root Access)" },
207
+ { val: "set_config", label: "Set Security Config (PUT)" }
208
+ ],
209
+ recovery: [
210
+ { val: "factory_reset", label: "Factory Reset [POST]" }
211
+ ]
212
+ };
213
+
214
+ $("#node-input-sysCategory").on("change", function () {
215
+ var cat = $(this).val();
216
+ var actions = sysActions[cat] || [];
217
+ var select = $("#node-input-sysAction");
218
+ select.empty();
219
+ actions.forEach(function (a) {
220
+ select.append($("<option></option>").attr("value", a.val).text(a.label));
221
+ });
222
+
223
+ // If the current sysAction value is not valid for this category, reset it
224
+ const currentVal = $("#node-input-sysAction").val();
225
+ let found = false;
226
+ actions.forEach(action => { if (action.val === currentVal) found = true; });
227
+
228
+ if (!found && actions.length > 0) {
229
+ $("#node-input-sysAction").val(actions[0].val);
230
+ } else if (found) {
231
+ $("#node-input-sysAction").val(currentVal);
232
+ }
233
+ });
234
+
235
+ // Provider Browsing
236
+ $("#btn-fetch-providers").click(function() {
237
+ var configId = $("#node-input-uosConfig").val();
238
+ if (!configId || configId == "_ADD_") {
239
+ RED.notify("Please select a u-OS Config first.", "error");
240
+ return;
241
+ }
242
+
243
+ var $btn = $(this); $btn.prop('disabled', true);
244
+
245
+ $.getJSON('uos-api/providers/' + configId, function(data) {
246
+ $btn.prop('disabled', false);
247
+ if (!Array.isArray(data)) { RED.notify("Invalid response", "error"); return; }
248
+
249
+ // Simple replacement with autocomplete or just a quick select list could be better
250
+ // For now, let's just show a notification list or simplistic approach
251
+ // Ideally we would replace the input with a select, but we want to allow custom text.
252
+ // Let's use a jQuery UI dialog or simple prompt-like behavior?
253
+ // Let's replace the Input with a Select momentarily to let user pick?
254
+
255
+ var providers = data.map(p => p.id);
256
+ // Use TypeInput's autocomplete? Or just standard array?
257
+ $("#node-input-dhProvider").autocomplete({
258
+ source: providers,
259
+ minLength: 0
260
+ }).autocomplete("search", "");
261
+
262
+ RED.notify("Found " + providers.length + " providers. Start typing to see suggestions.", "success");
263
+
264
+ }).fail(function(jqxhr) {
265
+ $btn.prop('disabled', false);
266
+ RED.notify("Failed to load providers: " + (jqxhr.responseJSON?.error || "Unknown"), "error");
267
+ });
268
+ });
269
+
270
+ // Variable Browsing
271
+ $("#btn-fetch-variables").click(function() {
272
+ var configId = $("#node-input-uosConfig").val();
273
+ var provider = $("#node-input-dhProvider").val();
274
+
275
+ if (!configId || configId == "_ADD_") { RED.notify("Please select a u-OS Config first.", "error"); return; }
276
+ if (!provider) { RED.notify("Please enter a Provider ID first.", "error"); return; }
277
+ var $btn = $(this); $btn.prop('disabled', true);
278
+ var $container = $("#var-browser-container");
279
+ var $list = $("#var-select-list");
280
+
281
+ // Switch to a div based list for checkboxes instead of select
282
+ // Or verify if current element is select? It is. Let's replace it dynamically or change HTML.
283
+ // Easier: Change HTML in previous step? No, let's manipulate DOM here or use the select with 'multiple' attribute.
284
+
285
+ // Let's use a standard multiple select
286
+ $list.attr("multiple", "multiple");
287
+ $list.css("height", "200px");
288
+
289
+ $container.show();
290
+ $list.html('<option>Loading...</option>');
291
+
292
+ $.getJSON('uos-api/variables/' + configId + '/' + provider, function(data) {
293
+ $btn.prop('disabled', false);
294
+ $list.empty();
295
+
296
+ if (data.length === 0) {
297
+ $list.append('<option disabled>No variables found</option>');
298
+ return;
299
+ }
300
+
301
+ // Sort
302
+ data.sort((a,b) => a.key.localeCompare(b.key));
303
+
304
+ data.forEach(v => {
305
+ $list.append($("<option>", { value: v.key, text: v.key }));
306
+ });
307
+
308
+ // Add a helper text/button to confirm selection
309
+ if ($("#btn-confirm-vars").length === 0) {
310
+ $container.append('<div style="margin-top:5px; text-align:right;"><small>Hold Ctrl for multi-select</small> <button
311
+ id="btn-confirm-vars" class="red-ui-button red-ui-button-small">Apply Selected</button></div>');
312
+
313
+ $("#btn-confirm-vars").click(function(e) {
314
+ e.preventDefault();
315
+ var selected = $list.val(); // Returns array for multiple select
316
+ if (selected && selected.length > 0) {
317
+ $("#node-input-dhVariable").val(selected.join(', '));
318
+ $container.hide();
319
+ }
320
+ });
321
+ }
322
+
323
+ // Filter Logic (refined for options)
324
+ $("#var-filter").off("keyup").on("keyup", function() {
325
+ var val = $(this).val().toLowerCase();
326
+ $list.find("option").each(function() {
327
+ var text = $(this).text().toLowerCase();
328
+ $(this).toggle(text.indexOf(val) > -1);
329
+ });
330
+ });
331
+
332
+ }).fail(function(jqxhr) {
333
+ $btn.prop('disabled', false);
334
+ $container.hide();
335
+ RED.notify("Failed to load variables: " + (jqxhr.responseJSON?.error || "Unknown"), "error");
336
+ });
337
+ });
338
+ }
339
+ });
340
+ </script>
341
+
342
+ <script type="text/html" data-template-name="uos-api">
343
+ <div class="form-row">
344
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
345
+ <input type="text" id="node-input-name" placeholder="Name">
346
+ </div>
347
+ <div class="form-row">
348
+ <label for="node-input-uosConfig"><i class="fa fa-cog"></i> u-OS Config</label>
349
+ <input type="text" id="node-input-uosConfig">
350
+ </div>
351
+ <div class="form-row">
352
+ <label for="node-input-mode"><i class="fa fa-sliders"></i> Mode</label>
353
+ <select id="node-input-mode">
354
+ <option value="datahub">DataHub Variables</option>
355
+ <option value="system">System Administration</option>
356
+ </select>
357
+ </div>
358
+
359
+ <!-- DataHub Mode -->
360
+ <div class="uos-mode-datahub">
361
+ <div class="form-row">
362
+ <label for="node-input-dhAction"><i class="fa fa-play"></i> Action</label>
363
+ <select id="node-input-dhAction">
364
+ <option value="read">Read Variable</option>
365
+ <option value="write">Write Variable</option>
366
+ <option value="list_providers">List Providers</option>
367
+ <option value="list_variables">List Variables</option>
368
+ </select>
369
+ </div>
370
+
371
+ <div class="form-row">
372
+ <label for="node-input-dhProvider"><i class="fa fa-server"></i> Provider</label>
373
+ <div style="display:flex; width:70%;">
374
+ <input type="text" id="node-input-dhProvider" placeholder="e.g. u_os_sbm" style="flex:1;">
375
+ <button id="btn-fetch-providers" class="red-ui-button" style="margin-left: 5px;" title="Load Providers"><i class="fa fa-refresh"></i></button>
376
+ </div>
377
+ </div>
378
+
379
+ <div class="form-row" id="row-dhVariable">
380
+ <label for="node-input-dhVariable"><i class="fa fa-tag"></i> Variable</label>
381
+ <div style="display:flex; width:70%;">
382
+ <input type="text" id="node-input-dhVariable" placeholder="e.g. digital_nameplate..." style="flex:1;">
383
+ <button id="btn-fetch-variables" class="red-ui-button" style="margin-left: 5px;" title="Browse Variables"><i class="fa fa-list"></i></button>
384
+ </div>
385
+ <div id="var-browser-container" style="display:none; margin-top:10px; border:1px solid #ccc; background:#fff; padding:5px;">
386
+ <input type="text" id="var-filter" placeholder="Filter variables..." style="width:100%; box-sizing:border-box; margin-bottom:5px;">
387
+ <select id="var-select-list" size="10" style="width:100%; border:1px solid #eee;"></select>
388
+ </div>
389
+ </div>
390
+ </div>
391
+
392
+ <!-- System Mode -->
393
+ <div class="uos-mode-system" style="display:none;">
394
+ <div class="form-row">
395
+ <label for="node-input-sysCategory"><i class="fa fa-folder"></i> Category</label>
396
+ <select id="node-input-sysCategory">
397
+ <option value="system">System</option>
398
+ <option value="network">Network</option>
399
+ <option value="logging">Logging</option>
400
+ <option value="security">Security</option>
401
+ <option value="recovery">Recovery</option>
402
+ </select>
403
+ </div>
404
+ <div class="form-row">
405
+ <label for="node-input-sysAction"><i class="fa fa-bolt"></i> Action</label>
406
+ <select id="node-input-sysAction">
407
+ <!-- Populated dynamically -->
408
+ </select>
409
+ </div>
410
+ </div>
411
+ </script>
412
+
413
+ <script type="text/html" data-help-name="uos-api">
414
+ <p>A unified node for interacting with Weidmüller u-OS HTTP APIs.</p>
415
+
416
+ <h3>Prerequisites</h3>
417
+ <p>This node uses OAuth2 for authentication. You must configure the <b>u-OS Config</b> node.</p>
418
+ <ul>
419
+ <li>For <b>DataHub</b> access, standard scopes are used.</li>
420
+ <li>For <b>System Administration</b>, you must check <b>Enable System Admin Access</b> in the Config node to grant permissions (<code>u-os-adm.*</code>).</li>
421
+ </ul>
422
+
423
+ <h3>Modes</h3>
424
+
425
+ <h4>1. DataHub Variables</h4>
426
+ <p>Access the DataHub Variable API directly over HTTP. Useful when NATS is not available or for simple request/response flows.</p>
427
+ <dl class="message-properties">
428
+ <dt>Read Variable</dt>
429
+ <dd>Gets the current value. Input <code>msg.provider</code> and <code>msg.variable</code> can override config.</dd>
430
+
431
+ <dt>Write Variable</dt>
432
+ <dd>Writes <code>msg.payload</code> to the variable. Must match the variable's expected type.</dd>
433
+
434
+ <dt>List Providers</dt>
435
+ <dd>Returns a list of all active DataHub providers.</dd>
436
+
437
+ <dt>List Variables</dt>
438
+ <dd>Returns a list of variables for a specific Provider.</dd>
439
+ </dl>
440
+
441
+ <h4>2. System Administration</h4>
442
+ <p>Manage the u-OS device settings and status. Corresponds to the System Administration API.</p>
443
+
444
+ <dl class="message-properties">
445
+ <dt>System</dt>
446
+ <dd>
447
+ <ul>
448
+ <li><b>Get Info</b>: Device model, CPU info, OS version.</li>
449
+ <li><b>Get Nameplate</b>: Serial number, Mac address, hardware version.</li>
450
+ <li><b>Get Disks</b>: Internal and external storage usage.</li>
451
+ <li><b>Reboot</b>: Restarts the device immediately (POST).</li>
452
+ </ul>
453
+ </dd>
454
+
455
+ <dt>Network</dt>
456
+ <dd>
457
+ <ul>
458
+ <li><b>Get State</b>: Live interface status (IPs, Up/Down).</li>
459
+ <li><b>Get Config</b>: Current saved configuration.</li>
460
+ <li><b>Set Config</b>: Overwrite full network config (PUT). <code>msg.payload</code> must be a valid <code>NetworkConfig</code> object.</li>
461
+ <li><b>Update Config</b>: Patch specific settings (PATCH). <code>msg.payload</code> must be a valid Merge-Patch JSON.</li>
462
+ </ul>
463
+ </dd>
464
+
465
+ <dt>Logging</dt>
466
+ <dd>
467
+ <ul>
468
+ <li><b>Get Entries</b>: Fetch system logs.</li>
469
+ <li><b>Create Report</b>: Triggers log archive generation.</li>
470
+ </ul>
471
+ </dd>
472
+
473
+ <dt>Security</dt>
474
+ <dd>
475
+ <ul>
476
+ <li><b>Get/Set Config</b>: Manage Root Access enablement (<code>{ root_access: { enabled: true } }</code>).</li>
477
+ </ul>
478
+ </dd>
479
+
480
+ <dt>Recovery</dt>
481
+ <dd><b>Factory Reset</b>: Resets device to factory defaults. Use with caution!</dd>
482
+ </dl>
483
+
484
+ <h3>Outputs</h3>
485
+ <dl class="message-properties">
486
+ <dt>payload <span class="property-type">object | string</span></dt>
487
+ <dd>The API response body.</dd>
488
+
489
+ <dt>statusCode <span class="property-type">number</span></dt>
490
+ <dd>HTTP Status Code (200, 202, 403, etc.).</dd>
491
+ </dl>
492
+ </script>
@@ -147,5 +147,77 @@ module.exports = function (RED) {
147
147
  });
148
148
  }
149
149
 
150
- RED.nodes.registerType('uos-http', UosHttpNode);
150
+ // Admin API for Node-RED Editor (Browsing)
151
+ RED.httpAdmin.get('/uos-api/providers/:config', RED.auth.needsPermission('uos-api.read'), async function (req, res) {
152
+ try {
153
+ const configNode = RED.nodes.getNode(req.params.config);
154
+ if (!configNode) {
155
+ res.status(404).json({ error: "Config node not found/deployed" });
156
+ return;
157
+ }
158
+
159
+ const token = await configNode.getToken();
160
+ if (!token) {
161
+ res.status(401).json({ error: "No Access Token" });
162
+ return;
163
+ }
164
+
165
+ const agent = new https.Agent({ rejectUnauthorized: false });
166
+ const url = `https://${configNode.host}:443/u-os-hub/api/v1/providers`;
167
+
168
+ const response = await fetch(url, {
169
+ headers: { 'Authorization': `Bearer ${token}` },
170
+ agent: agent
171
+ });
172
+
173
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
174
+ const json = await response.json();
175
+ res.json(json.items || []);
176
+
177
+ } catch (err) {
178
+ res.status(500).json({ error: err.message });
179
+ }
180
+ });
181
+
182
+ RED.httpAdmin.get('/uos-api/variables/:config/:provider', RED.auth.needsPermission('uos-api.read'), async function (req, res) {
183
+ try {
184
+ const configNode = RED.nodes.getNode(req.params.config);
185
+ const provider = req.params.provider;
186
+
187
+ if (!configNode) {
188
+ res.status(404).json({ error: "Config node not found/deployed" });
189
+ return;
190
+ }
191
+
192
+ const token = await configNode.getToken();
193
+ if (!token) {
194
+ res.status(401).json({ error: "No Access Token" });
195
+ return;
196
+ }
197
+
198
+ const agent = new https.Agent({ rejectUnauthorized: false });
199
+ const url = `https://${configNode.host}:443/u-os-hub/api/v1/providers/${provider}/variables`;
200
+
201
+ const response = await fetch(url, {
202
+ headers: { 'Authorization': `Bearer ${token}` },
203
+ agent: agent
204
+ });
205
+
206
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
207
+ const json = await response.json();
208
+
209
+ // Map to simple structure { key: "foo", type: "int" }
210
+ const simplified = (json.items || []).map(v => ({
211
+ key: v.key,
212
+ type: v.data_type
213
+ }));
214
+
215
+ res.json(simplified);
216
+
217
+ } catch (err) {
218
+ res.status(500).json({ error: err.message });
219
+ }
220
+ });
221
+
222
+ RED.nodes.registerType('uos-api', UosHttpNode);
151
223
  };
@@ -133,6 +133,8 @@ module.exports = function (RED) {
133
133
 
134
134
  this.pendingTokenRequest = (async () => {
135
135
  try {
136
+ this.log(`Requesting Token with Scopes: ${this.scope}`); // DEBUG Log
137
+
136
138
  const params = new URLSearchParams({
137
139
  grant_type: 'client_credentials',
138
140
  scope: this.scope,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-uos-nats",
3
- "version": "1.4.0",
3
+ "version": "1.4.6",
4
4
  "description": "Node-RED nodes for Weidmüller u-OS Data Hub. Read, write, and provide variables via NATS protocol with OAuth2 authentication. Features: Variable Key resolution, custom icons, example flows, and provider definition caching.",
5
5
  "author": {
6
6
  "name": "IoTUeli",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "flatbuffers": "^25.9.23",
44
- "nats": "^2.19.0",
45
- "node-fetch": "^2.6.7"
44
+ "nats": "^2.29.3",
45
+ "node-fetch": "^2.7.0"
46
46
  },
47
47
  "files": [
48
48
  "nodes/",
@@ -57,7 +57,7 @@
57
57
  "datahub-input": "nodes/datahub-input.js",
58
58
  "datahub-output": "nodes/datahub-output.js",
59
59
  "datahub-write": "nodes/datahub-write.js",
60
- "uos-http": "nodes/uos-http.js"
60
+ "uos-api": "nodes/uos-api.js"
61
61
  }
62
62
  },
63
63
  "scripts": {
@@ -1,150 +0,0 @@
1
- <script type="text/javascript">
2
- RED.nodes.registerType('uos-http', {
3
- category: 'Web Services',
4
- color: '#a6bbcf',
5
- defaults: {
6
- name: { value: "" },
7
- uosConfig: { type: "uos-config", required: true },
8
- mode: { value: "datahub" },
9
-
10
- // DataHub
11
- dhAction: { value: "read" },
12
- dhProvider: { value: "" },
13
- dhVariable: { value: "" },
14
-
15
- // System
16
- sysCategory: { value: "system" },
17
- sysAction: { value: "info" }
18
- },
19
- inputs: 1,
20
- outputs: 1,
21
- icon: "uos-api.svg",
22
- label: function () {
23
- return this.name || "u-OS API";
24
- },
25
- oneditprepare: function () {
26
- $("#node-input-mode").on("change", function () {
27
- var mode = $(this).val();
28
- if (mode === "datahub") {
29
- $(".uos-mode-datahub").show();
30
- $(".uos-mode-system").hide();
31
- } else {
32
- $(".uos-mode-datahub").hide();
33
- $(".uos-mode-system").show();
34
- }
35
- });
36
-
37
- // System Action Dynamic Options
38
- const sysActions = {
39
- system: [
40
- { val: "info", label: "Get System Info" },
41
- { val: "nameplate", label: "Get Nameplate" },
42
- { val: "disks", label: "Get Disks" },
43
- { val: "reboot", label: "Reboot System (POST)" }
44
- ],
45
- network: [
46
- { val: "get_state", label: "Get State" },
47
- { val: "get_config", label: "Get Config" },
48
- { val: "set_config", label: "Set Config (PUT)" },
49
- { val: "update_config", label: "Update Config (PATCH)" }
50
- ],
51
- logging: [
52
- { val: "entries", label: "Get Entries" },
53
- { val: "sources", label: "Get Sources" },
54
- { val: "create_report", label: "Create Report" }
55
- ],
56
- security: [
57
- { val: "get_config", label: "Get Config" },
58
- { val: "set_config", label: "Set Config (PUT)" }
59
- ],
60
- recovery: [
61
- { val: "factory_reset", label: "Factory Reset" }
62
- ]
63
- };
64
-
65
- $("#node-input-sysCategory").on("change", function () {
66
- var cat = $(this).val();
67
- var actions = sysActions[cat] || [];
68
- var select = $("#node-input-sysAction");
69
- select.empty();
70
- actions.forEach(function (a) {
71
- select.append($("<option></option>").attr("value", a.val).text(a.label));
72
- });
73
- // Restore previous value if feasible, otherwise select first
74
- // (Logic simplified here)
75
- });
76
- }
77
- });
78
- </script>
79
-
80
- <script type="text/html" data-template-name="uos-http">
81
- <div class="form-row">
82
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
83
- <input type="text" id="node-input-name" placeholder="Name">
84
- </div>
85
- <div class="form-row">
86
- <label for="node-input-uosConfig"><i class="fa fa-cog"></i> u-OS Config</label>
87
- <input type="text" id="node-input-uosConfig">
88
- </div>
89
- <div class="form-row">
90
- <label for="node-input-mode"><i class="fa fa-sliders"></i> Mode</label>
91
- <select id="node-input-mode">
92
- <option value="datahub">DataHub Variables</option>
93
- <option value="system">System Administration</option>
94
- </select>
95
- </div>
96
-
97
- <!-- DataHub Mode -->
98
- <div class="uos-mode-datahub">
99
- <div class="form-row">
100
- <label for="node-input-dhAction"><i class="fa fa-play"></i> Action</label>
101
- <select id="node-input-dhAction">
102
- <option value="read">Read Variable</option>
103
- <option value="write">Write Variable</option>
104
- <option value="list_providers">List Providers</option>
105
- <option value="list_variables">List Variables</option>
106
- </select>
107
- </div>
108
- <div class="form-row">
109
- <label for="node-input-dhProvider"><i class="fa fa-server"></i> Provider</label>
110
- <input type="text" id="node-input-dhProvider" placeholder="e.g. u_os_sbm">
111
- </div>
112
- <div class="form-row">
113
- <label for="node-input-dhVariable"><i class="fa fa-tag"></i> Variable</label>
114
- <input type="text" id="node-input-dhVariable" placeholder="e.g. digital_nameplate.software_version">
115
- </div>
116
- </div>
117
-
118
- <!-- System Mode -->
119
- <div class="uos-mode-system" style="display:none;">
120
- <div class="form-row">
121
- <label for="node-input-sysCategory"><i class="fa fa-folder"></i> Category</label>
122
- <select id="node-input-sysCategory">
123
- <option value="system">System</option>
124
- <option value="network">Network</option>
125
- <option value="logging">Logging</option>
126
- <option value="security">Security</option>
127
- <option value="recovery">Recovery</option>
128
- </select>
129
- </div>
130
- <div class="form-row">
131
- <label for="node-input-sysAction"><i class="fa fa-bolt"></i> Action</label>
132
- <select id="node-input-sysAction">
133
- <!-- Populated dynamically -->
134
- </select>
135
- </div>
136
- </div>
137
- </script>
138
-
139
- <script type="text/html" data-help-name="uos-http">
140
- <p>A unified node for u-OS HTTP APIs.</p>
141
-
142
- <h3>Modes</h3>
143
- <dl class="message-properties">
144
- <dt>DataHub Variables</dt>
145
- <dd>Access values via the standard DataHub HTTP API. Use for devices that don't support NATS or simple polling.</dd>
146
-
147
- <dt>System Administration</dt>
148
- <dd>Manage the u-OS device: Network, Logs, Security, etc. Requires <b>Enable System Admin</b> in config.</dd>
149
- </dl>
150
- </script>