@frangoteam/fuxa-min 1.2.11 → 1.3.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.
Files changed (67) hide show
  1. package/api/auth/index.js +141 -3
  2. package/api/command/index.js +1 -1
  3. package/api/index.js +21 -2
  4. package/api/jwt-helper.js +3 -1
  5. package/api/resources/index.js +19 -2
  6. package/api/scripts/index.js +7 -3
  7. package/dist/3rdpartylicenses.txt +139 -7
  8. package/dist/assets/i18n/de.json +10 -0
  9. package/dist/assets/i18n/en.json +21 -3
  10. package/dist/assets/i18n/es.json +12 -0
  11. package/dist/assets/i18n/fr.json +11 -0
  12. package/dist/assets/i18n/ja.json +16 -6
  13. package/dist/assets/i18n/ko.json +12 -0
  14. package/dist/assets/i18n/pt.json +9 -2
  15. package/dist/assets/i18n/ru.json +11 -0
  16. package/dist/assets/i18n/sv.json +11 -1
  17. package/dist/assets/i18n/tr.json +8 -1
  18. package/dist/assets/i18n/ua.json +9 -2
  19. package/dist/assets/i18n/zh-cn.json +11 -0
  20. package/dist/assets/i18n/zh-tw.json +12 -1
  21. package/dist/assets/lib/svgeditor/fuxa-editor.min.js +17 -23
  22. package/dist/index.html +2 -2
  23. package/dist/main.72bdfed42c527918.js +329 -0
  24. package/dist/polyfills.d7de05f9af2fb559.js +1 -0
  25. package/dist/{runtime.8ef63094e52a66ba.js → runtime.9136a61a9b98f987.js} +1 -1
  26. package/dist/{scripts.40b60f02658462e4.js → scripts.d9e6ee984bf6f3b7.js} +1 -1
  27. package/dist/styles.545e37beb3e671ba.css +1 -0
  28. package/docs/openapi.yaml +3 -0
  29. package/integrations/node-red/index.js +4 -5
  30. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.html +56 -5
  31. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.js +8 -2
  32. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.html +56 -5
  33. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.js +12 -12
  34. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.html +56 -5
  35. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.js +14 -10
  36. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.html +69 -14
  37. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.js +44 -12
  38. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.html +56 -5
  39. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.js +24 -20
  40. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.html +72 -13
  41. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.js +33 -7
  42. package/main.js +38 -17
  43. package/package.json +10 -5
  44. package/runtime/alarms/alarmstorage.js +45 -30
  45. package/runtime/apikeys/apiKeysStorage.js +34 -22
  46. package/runtime/devices/adsclient/index.js +1 -1
  47. package/runtime/devices/ethernetip/index.js +1 -1
  48. package/runtime/devices/gpio/index.js +1 -1
  49. package/runtime/devices/odbc/index.js +14 -9
  50. package/runtime/devices/s7/index.js +2 -2
  51. package/runtime/devices/template/index.js +14 -14
  52. package/runtime/notificator/notifystorage.js +34 -23
  53. package/runtime/project/index.js +16 -0
  54. package/runtime/project/prjstorage.js +78 -40
  55. package/runtime/scripts/index.js +6 -2
  56. package/runtime/storage/calculator.js +17 -13
  57. package/runtime/storage/daqstorage.js +28 -2
  58. package/runtime/storage/influxdb/index.js +8 -3
  59. package/runtime/storage/questdb/index.js +224 -0
  60. package/runtime/storage/sqlite/index.js +11 -8
  61. package/runtime/storage/tdengine/index.js +24 -9
  62. package/runtime/users/usrstorage.js +65 -61
  63. package/runtime/utils.js +5 -0
  64. package/settings.default.js +8 -2
  65. package/dist/main.92522279642ef880.js +0 -329
  66. package/dist/polyfills.c8e7db9850a3ad8b.js +0 -1
  67. package/dist/styles.03cc550382689976.css +0 -1
@@ -152,12 +152,11 @@ async function mountNodeRedIfInstalled({ app, server, settings, runtime, logger,
152
152
  });
153
153
  };
154
154
 
155
- // Allow public dashboard UI and socket.io; require JWT or API key for admin/editor/flows when security is enabled
155
+ // Allow only dashboard routes as public; require JWT or API key for admin/editor/flows when security is enabled
156
156
  const allowDashboard = (req, res, next) => {
157
- const url = req.originalUrl || req.url || req.path;
158
-
159
- // Public dashboard UI and its HTTP APIs (served from httpNodeRoot/ui.path)
160
- if (url.includes('/dashboard') || url.includes('/socket.io')) return next();
157
+ // Public dashboard UI and its HTTP APIs (served from httpNodeRoot/ui.path).
158
+ // baseUrl comes from Express mount point and is not affected by query/path tricks.
159
+ if (req.baseUrl === '/dashboard') return next();
161
160
 
162
161
  if (!settings.secureEnabled || settings.nodeRedAuthMode === 'legacy-open') {
163
162
  return next();
@@ -4,7 +4,8 @@
4
4
  color: '#a6bbcf',
5
5
  defaults: {
6
6
  name: {value:""},
7
- tag: {value:"", required:true},
7
+ tag: {value:""}, // Keep: tag name for display and backward compatibility
8
+ tagId: {value:""}, // New: unique identifier (optional, for backward compatibility)
8
9
  from: {value:""},
9
10
  to: {value:""}
10
11
  },
@@ -12,17 +13,65 @@
12
13
  outputs:1,
13
14
  icon: "white-globe.png",
14
15
  label: function() {
15
- return this.name||this.tag||"get daq";
16
+ if (this.name) {
17
+ return this.name;
18
+ }
19
+ // Display tag name or tagId
20
+ return this.tag || this.tagId || "get daq";
16
21
  },
17
22
  oneditprepare: function() {
23
+ var node = this;
24
+ var tagMap = {}; // Use tag.id as key to avoid tag.name duplication issues
25
+
18
26
  $.getJSON('/nodered/fuxa/devices', function(data) {
19
27
  var datalist = $('#fuxa-tags-daq');
20
28
  datalist.empty();
29
+
21
30
  data.forEach(function(device) {
22
31
  device.tags.forEach(function(tag) {
23
- datalist.append('<option value="' + tag.name + '">' + device.name + ' - ' + tag.name + '</option>');
32
+ var fullName = device.name + ' - ' + tag.name;
33
+ // datalist option value format: tag.name(tag.id)
34
+ var optionValue = tag.name + '(' + tag.id + ')';
35
+ datalist.append('<option value="' + optionValue + '">' + fullName + '</option>');
36
+ tagMap[tag.id] = {
37
+ name: tag.name,
38
+ deviceName: device.name,
39
+ fullName: fullName
40
+ };
24
41
  });
25
42
  });
43
+
44
+ // Listen for tagSelected input changes
45
+ $('#node-input-tagSelected').on('change', function() {
46
+ var selectedValue = $(this).val();
47
+ // Parse format: tag.name(tag.id)
48
+ var match = selectedValue.match(/^(.+)\(([^)]+)\)$/);
49
+ if (match) {
50
+ var tagName = match[1];
51
+ var tagId = match[2];
52
+
53
+ // Set the actual stored fields
54
+ $('#node-input-tag').val(tagName);
55
+ $('#node-input-tagId').val(tagId);
56
+ }
57
+ });
58
+
59
+ // Initialize display (when editing existing node)
60
+ if (node.tagId && tagMap[node.tagId]) {
61
+ // Has tagId, construct tagSelected value
62
+ var tagName = node.tag || tagMap[node.tagId].name;
63
+ $('#node-input-tagSelected').val(tagName + '(' + node.tagId + ')');
64
+ } else if (node.tag && !node.tagId) {
65
+ // Old node: only has tag (tag.name), need to find corresponding tagId
66
+ // Note: if there are duplicate names, only the first match will be found
67
+ for (var tagId in tagMap) {
68
+ if (tagMap[tagId].name === node.tag) {
69
+ $('#node-input-tagSelected').val(node.tag + '(' + tagId + ')');
70
+ $('#node-input-tagId').val(tagId);
71
+ break;
72
+ }
73
+ }
74
+ }
26
75
  });
27
76
 
28
77
  // Convert timestamp format for datetime-local inputs
@@ -64,10 +113,12 @@
64
113
  <input type="text" id="node-input-name" placeholder="Name">
65
114
  </div>
66
115
  <div class="form-row">
67
- <label for="node-input-tag"><i class="fa fa-tag"></i> Tag</label>
68
- <input type="text" id="node-input-tag" list="fuxa-tags-daq" placeholder="Tag Name">
116
+ <label for="node-input-tagSelected"><i class="fa fa-tag"></i> Tag</label>
117
+ <input type="text" id="node-input-tagSelected" list="fuxa-tags-daq" placeholder="Select Tag">
69
118
  <datalist id="fuxa-tags-daq"></datalist>
70
119
  </div>
120
+ <input type="hidden" id="node-input-tag">
121
+ <input type="hidden" id="node-input-tagId">
71
122
  <div class="form-row">
72
123
  <label for="node-input-from"><i class="fa fa-clock-o"></i> From (timestamp)</label>
73
124
  <input type="datetime-local" id="node-input-from" placeholder="From timestamp (optional)">
@@ -6,7 +6,13 @@ module.exports = function(RED) {
6
6
 
7
7
  this.on('input', async function(msg) {
8
8
  try {
9
- var tagId = fuxa.getTagId(config.tag, null);
9
+ // Prefer config.tagId, fallback to config.tag for backward compatibility
10
+ var tagId = config.tagId;
11
+ if (!tagId && config.tag) {
12
+ // Backward compatibility: old nodes use tag.name, need to convert to tagId
13
+ tagId = fuxa.getTagId(config.tag, null);
14
+ }
15
+
10
16
  if (tagId) {
11
17
  var fromts = config.from || msg.from || Date.now() - 3600000; // default 1 hour ago
12
18
  var tots = config.to || msg.to || Date.now();
@@ -14,7 +20,7 @@ module.exports = function(RED) {
14
20
  msg.payload = data;
15
21
  node.send(msg);
16
22
  } else {
17
- node.error('Tag not found: ' + config.tag, msg);
23
+ node.error('Tag not found: ' + (config.tag || config.tagId), msg);
18
24
  }
19
25
  } catch (err) {
20
26
  node.error(err, msg);
@@ -4,23 +4,72 @@
4
4
  color: '#a6bbcf',
5
5
  defaults: {
6
6
  name: {value:""},
7
- tag: {value:"", required:true}
7
+ tag: {value:""}, // Keep: tag name for display and backward compatibility
8
+ tagId: {value:""} // New: unique identifier (optional, for backward compatibility)
8
9
  },
9
10
  inputs:0,
10
11
  outputs:1,
11
12
  icon: "white-globe.png",
12
13
  label: function() {
13
- return this.name||this.tag||"get tag change";
14
+ if (this.name) {
15
+ return this.name;
16
+ }
17
+ // Display tag name or tagId
18
+ return this.tag || this.tagId || "get tag change";
14
19
  },
15
20
  oneditprepare: function() {
21
+ var node = this;
22
+ var tagMap = {}; // Use tag.id as key to avoid tag.name duplication issues
23
+
16
24
  $.getJSON('/nodered/fuxa/devices', function(data) {
17
25
  var datalist = $('#fuxa-change-tags');
18
26
  datalist.empty();
27
+
19
28
  data.forEach(function(device) {
20
29
  device.tags.forEach(function(tag) {
21
- datalist.append('<option value="' + tag.name + '">' + device.name + ' - ' + tag.name + '</option>');
30
+ var fullName = device.name + ' - ' + tag.name;
31
+ // datalist option value format: tag.name(tag.id)
32
+ var optionValue = tag.name + '(' + tag.id + ')';
33
+ datalist.append('<option value="' + optionValue + '">' + fullName + '</option>');
34
+ tagMap[tag.id] = {
35
+ name: tag.name,
36
+ deviceName: device.name,
37
+ fullName: fullName
38
+ };
22
39
  });
23
40
  });
41
+
42
+ // Listen for tagSelected input changes
43
+ $('#node-input-tagSelected').on('change', function() {
44
+ var selectedValue = $(this).val();
45
+ // Parse format: tag.name(tag.id)
46
+ var match = selectedValue.match(/^(.+)\(([^)]+)\)$/);
47
+ if (match) {
48
+ var tagName = match[1];
49
+ var tagId = match[2];
50
+
51
+ // Set the actual stored fields
52
+ $('#node-input-tag').val(tagName);
53
+ $('#node-input-tagId').val(tagId);
54
+ }
55
+ });
56
+
57
+ // Initialize display (when editing existing node)
58
+ if (node.tagId && tagMap[node.tagId]) {
59
+ // Has tagId, construct tagSelected value
60
+ var tagName = node.tag || tagMap[node.tagId].name;
61
+ $('#node-input-tagSelected').val(tagName + '(' + node.tagId + ')');
62
+ } else if (node.tag && !node.tagId) {
63
+ // Old node: only has tag (tag.name), need to find corresponding tagId
64
+ // Note: if there are duplicate names, only the first match will be found
65
+ for (var tagId in tagMap) {
66
+ if (tagMap[tagId].name === node.tag) {
67
+ $('#node-input-tagSelected').val(node.tag + '(' + tagId + ')');
68
+ $('#node-input-tagId').val(tagId);
69
+ break;
70
+ }
71
+ }
72
+ }
24
73
  });
25
74
  }
26
75
  });
@@ -32,10 +81,12 @@
32
81
  <input type="text" id="node-input-name" placeholder="Name">
33
82
  </div>
34
83
  <div class="form-row">
35
- <label for="node-input-tag"><i class="fa fa-tag"></i> Tag</label>
36
- <input type="text" id="node-input-tag" list="fuxa-change-tags" placeholder="Tag Name">
84
+ <label for="node-input-tagSelected"><i class="fa fa-tag"></i> Tag</label>
85
+ <input type="text" id="node-input-tagSelected" list="fuxa-change-tags" placeholder="Select Tag">
37
86
  <datalist id="fuxa-change-tags"></datalist>
38
87
  </div>
88
+ <input type="hidden" id="node-input-tag">
89
+ <input type="hidden" id="node-input-tagId">
39
90
  </script>
40
91
 
41
92
  <script type="text/x-red" data-help-name="get-tag-change">
@@ -7,26 +7,26 @@ module.exports = function(RED) {
7
7
  // Store previous values to detect actual changes
8
8
  var previousValues = {};
9
9
 
10
+ // Prefer config.tagId, fallback to config.tag for backward compatibility
11
+ var configuredTagId = config.tagId;
12
+ if (!configuredTagId && config.tag) {
13
+ // Backward compatibility: old nodes use tag.name, need to convert to tagId
14
+ configuredTagId = fuxa.getTagId(config.tag, null);
15
+ }
16
+
10
17
  // Initialize previous value for the configured tag
11
- if (config.tag) {
12
- var tagId = fuxa.getTagId(config.tag, null);
13
- if (tagId) {
14
- // Initialize previous value
15
- var initialValue = fuxa.getTag(tagId);
16
- if (initialValue !== undefined) {
17
- previousValues[tagId] = initialValue;
18
- }
18
+ if (configuredTagId) {
19
+ var initialValue = fuxa.getTag(configuredTagId);
20
+ if (initialValue !== undefined) {
21
+ previousValues[configuredTagId] = initialValue;
19
22
  }
20
23
  }
21
24
 
22
- // Event listener for device value changes (raw events from devices)
25
+ // Event listener for device value changes (raw events from devices)
23
26
  var deviceEventListener = function(deviceEvent) {
24
27
  try {
25
28
  // deviceEvent format: { id: deviceId, values: { tagId: tagObject, ... } }
26
29
  if (deviceEvent && deviceEvent.values) {
27
- // Check if any of the changed values match our configured tag
28
- var configuredTagId = fuxa.getTagId(config.tag, null);
29
-
30
30
  if (configuredTagId && deviceEvent.values[configuredTagId]) {
31
31
  var tagData = deviceEvent.values[configuredTagId];
32
32
 
@@ -4,23 +4,72 @@
4
4
  color: '#a6bbcf',
5
5
  defaults: {
6
6
  name: {value:""},
7
- tag: {value:"", required:true}
7
+ tag: {value:""}, // Keep: tag name for display and backward compatibility
8
+ tagId: {value:""} // New: unique identifier (optional, for backward compatibility)
8
9
  },
9
10
  inputs:1,
10
11
  outputs:1,
11
12
  icon: "white-globe.png",
12
13
  label: function() {
13
- return this.name||this.tag||"get tag daq settings";
14
+ if (this.name) {
15
+ return this.name;
16
+ }
17
+ // Display tag name or tagId
18
+ return this.tag || this.tagId || "get tag daq settings";
14
19
  },
15
20
  oneditprepare: function() {
21
+ var node = this;
22
+ var tagMap = {}; // Use tag.id as key to avoid tag.name duplication issues
23
+
16
24
  $.getJSON('/nodered/fuxa/devices', function(data) {
17
25
  var datalist = $('#fuxa-tags-daq-settings');
18
26
  datalist.empty();
27
+
19
28
  data.forEach(function(device) {
20
29
  device.tags.forEach(function(tag) {
21
- datalist.append('<option value="' + tag.name + '">' + device.name + ' - ' + tag.name + '</option>');
30
+ var fullName = device.name + ' - ' + tag.name;
31
+ // datalist option value format: tag.name(tag.id)
32
+ var optionValue = tag.name + '(' + tag.id + ')';
33
+ datalist.append('<option value="' + optionValue + '">' + fullName + '</option>');
34
+ tagMap[tag.id] = {
35
+ name: tag.name,
36
+ deviceName: device.name,
37
+ fullName: fullName
38
+ };
22
39
  });
23
40
  });
41
+
42
+ // Listen for tagSelected input changes
43
+ $('#node-input-tagSelected').on('change', function() {
44
+ var selectedValue = $(this).val();
45
+ // Parse format: tag.name(tag.id)
46
+ var match = selectedValue.match(/^(.+)\(([^)]+)\)$/);
47
+ if (match) {
48
+ var tagName = match[1];
49
+ var tagId = match[2];
50
+
51
+ // Set the actual stored fields
52
+ $('#node-input-tag').val(tagName);
53
+ $('#node-input-tagId').val(tagId);
54
+ }
55
+ });
56
+
57
+ // Initialize display (when editing existing node)
58
+ if (node.tagId && tagMap[node.tagId]) {
59
+ // Has tagId, construct tagSelected value
60
+ var tagName = node.tag || tagMap[node.tagId].name;
61
+ $('#node-input-tagSelected').val(tagName + '(' + node.tagId + ')');
62
+ } else if (node.tag && !node.tagId) {
63
+ // Old node: only has tag (tag.name), need to find corresponding tagId
64
+ // Note: if there are duplicate names, only the first match will be found
65
+ for (var tagId in tagMap) {
66
+ if (tagMap[tagId].name === node.tag) {
67
+ $('#node-input-tagSelected').val(node.tag + '(' + tagId + ')');
68
+ $('#node-input-tagId').val(tagId);
69
+ break;
70
+ }
71
+ }
72
+ }
24
73
  });
25
74
  }
26
75
  });
@@ -32,10 +81,12 @@
32
81
  <input type="text" id="node-input-name" placeholder="Name">
33
82
  </div>
34
83
  <div class="form-row">
35
- <label for="node-input-tag"><i class="fa fa-tag"></i> Tag</label>
36
- <input type="text" id="node-input-tag" list="fuxa-tags-daq-settings" placeholder="Tag Name">
84
+ <label for="node-input-tagSelected"><i class="fa fa-tag"></i> Tag</label>
85
+ <input type="text" id="node-input-tagSelected" list="fuxa-tags-daq-settings" placeholder="Select Tag">
37
86
  <datalist id="fuxa-tags-daq-settings"></datalist>
38
87
  </div>
88
+ <input type="hidden" id="node-input-tag">
89
+ <input type="hidden" id="node-input-tagId">
39
90
  </script>
40
91
 
41
92
  <script type="text/x-red" data-help-name="get-tag-daq-settings">
@@ -6,18 +6,22 @@ module.exports = function(RED) {
6
6
 
7
7
  this.on('input', async function(msg) {
8
8
  try {
9
- var tagName = config.tag || msg.tag;
10
- if (tagName) {
11
- var tagId = fuxa.getTagId(tagName, null);
12
- if (tagId) {
13
- var settings = await fuxa.getTagDaqSettings(tagId);
14
- msg.payload = settings;
15
- node.send(msg);
16
- } else {
17
- node.error('Tag not found: ' + tagName, msg);
9
+ // Prefer config.tagId, fallback to config.tag or msg.tag for backward compatibility
10
+ var tagId = config.tagId;
11
+ if (!tagId) {
12
+ var tagName = config.tag || msg.tag;
13
+ if (tagName) {
14
+ // Backward compatibility: old nodes use tag.name, need to convert to tagId
15
+ tagId = fuxa.getTagId(tagName, null);
18
16
  }
17
+ }
18
+
19
+ if (tagId) {
20
+ var settings = await fuxa.getTagDaqSettings(tagId);
21
+ msg.payload = settings;
22
+ node.send(msg);
19
23
  } else {
20
- node.error('Tag name not specified', msg);
24
+ node.error('Tag not found: ' + (config.tag || msg.tag || config.tagId), msg);
21
25
  }
22
26
  } catch (err) {
23
27
  node.error(err, msg);
@@ -1,27 +1,69 @@
1
1
  <script type="text/javascript">
2
- RED.nodes.registerType('get-tag',{
2
+ RED.nodes.registerType('get-tag', {
3
3
  category: 'FUXA',
4
4
  color: '#a6bbcf',
5
5
  defaults: {
6
- name: {value:""},
7
- tag: {value:"", required:true}
6
+ name: {value: ""},
7
+ tag: {value: ""},
8
+ tagId: {value: ""}
8
9
  },
9
- inputs:1,
10
- outputs:1,
10
+ inputs: 1,
11
+ outputs: 1,
11
12
  icon: "white-globe.png",
12
13
  label: function() {
13
- return this.name||this.tag||"get tag";
14
+ if (this.name) {
15
+ return this.name;
16
+ }
17
+ return this.tag || this.tagId || "get tag";
14
18
  },
15
19
  oneditprepare: function() {
20
+ var node = this;
21
+ var tagMap = {};
22
+
16
23
  $.getJSON('/nodered/fuxa/devices', function(data) {
17
24
  var datalist = $('#fuxa-tags');
18
25
  datalist.empty();
19
- data.forEach(function(device) {
20
- device.tags.forEach(function(tag) {
21
- datalist.append('<option value="' + tag.name + '">' + device.name + ' - ' + tag.name + '</option>');
26
+
27
+ (data || []).forEach(function(device) {
28
+ (device.tags || []).forEach(function(tag) {
29
+ var fullName = device.name + ' - ' + tag.name;
30
+ var optionValue = tag.name + '(' + tag.id + ')';
31
+ datalist.append('<option value="' + optionValue + '">' + fullName + '</option>');
32
+ tagMap[tag.id] = {
33
+ name: tag.name,
34
+ deviceName: device.name,
35
+ fullName: fullName
36
+ };
22
37
  });
23
38
  });
39
+
40
+ $('#node-input-tagSelected').on('change', function() {
41
+ var selectedValue = $(this).val();
42
+ var match = selectedValue.match(/^(.+)\(([^)]+)\)$/);
43
+ if (match) {
44
+ $('#node-input-tag').val(match[1]);
45
+ $('#node-input-tagId').val(match[2]);
46
+ } else if (!selectedValue || !selectedValue.trim()) {
47
+ $('#node-input-tag').val('');
48
+ $('#node-input-tagId').val('');
49
+ }
50
+ });
51
+
52
+ if (node.tagId && tagMap[node.tagId]) {
53
+ var tagName = node.tag || tagMap[node.tagId].name;
54
+ $('#node-input-tagSelected').val(tagName + '(' + node.tagId + ')');
55
+ } else if (node.tag && !node.tagId) {
56
+ for (var id in tagMap) {
57
+ if (tagMap[id].name === node.tag) {
58
+ $('#node-input-tagSelected').val(node.tag + '(' + id + ')');
59
+ $('#node-input-tagId').val(id);
60
+ break;
61
+ }
62
+ }
63
+ }
24
64
  });
65
+
66
+ $('#node-input-tagSelected').attr('placeholder', 'Optional - leave empty to use msg.topic');
25
67
  }
26
68
  });
27
69
  </script>
@@ -32,13 +74,26 @@
32
74
  <input type="text" id="node-input-name" placeholder="Name">
33
75
  </div>
34
76
  <div class="form-row">
35
- <label for="node-input-tag"><i class="fa fa-tag"></i> Tag</label>
36
- <input type="text" id="node-input-tag" list="fuxa-tags" placeholder="Tag Name">
77
+ <label for="node-input-tagSelected"><i class="fa fa-tag"></i> Tag</label>
78
+ <input type="text" id="node-input-tagSelected" list="fuxa-tags" placeholder="Optional - leave empty to use msg.topic">
37
79
  <datalist id="fuxa-tags"></datalist>
38
80
  </div>
81
+ <input type="hidden" id="node-input-tag">
82
+ <input type="hidden" id="node-input-tagId">
83
+ <div class="form-row">
84
+ <div style="margin-left:105px;font-size:12px;color:#666;">
85
+ If <b>Tag</b> is empty, the node uses <code>msg.topic</code>.
86
+ </div>
87
+ </div>
39
88
  </script>
40
89
 
41
90
  <script type="text/x-red" data-help-name="get-tag">
42
- <p>Get the value of a FUXA tag.</p>
43
- <p>The tag value is set to <code>msg.payload</code>.</p>
44
- </script>
91
+ <p>Gets the value of a FUXA tag.</p>
92
+ <ul>
93
+ <li>If <b>Tag</b> is set in the node, that tag is used.</li>
94
+ <li>If <b>Tag</b> is empty, <code>msg.topic</code> is used.</li>
95
+ </ul>
96
+ <p>The tag value is returned in <code>msg.payload</code>.</p>
97
+ <p>The resolved tag name is always set on <code>msg.topic</code>.</p>
98
+ <pre>msg.topic = "V2";</pre>
99
+ </script>
@@ -1,25 +1,57 @@
1
1
  module.exports = function(RED) {
2
2
  function FuxaGetTagNode(config) {
3
3
  RED.nodes.createNode(this, config);
4
- var node = this;
4
+ const node = this;
5
+
5
6
  // Access FUXA functions from global context
6
- var fuxa = RED.settings.functionGlobalContext.fuxa;
7
+ const fuxa = RED.settings?.functionGlobalContext?.fuxa;
8
+
9
+ node.on("input", function(msg, send, done) {
10
+ send = send || node.send.bind(node);
7
11
 
8
- this.on('input', function(msg) {
9
12
  try {
10
- var tagId = fuxa.getTagId(config.tag, null);
11
- if (tagId) {
12
- var value = fuxa.getTag(tagId);
13
- msg.payload = value;
14
- msg.topic = config.tag; // Set topic to tag name for join operations
15
- node.send(msg);
16
- } else {
17
- node.error('Tag not found: ' + config.tag, msg);
13
+ if (!fuxa) {
14
+ node.error("FUXA not available in functionGlobalContext", msg);
15
+ return done && done();
16
+ }
17
+
18
+ const uiTag = (typeof config.tag === "string") ? config.tag.trim() : "";
19
+ const topicTag = (typeof msg.topic === "string") ? msg.topic.trim() : "";
20
+ const tagRef = uiTag || topicTag;
21
+ let tagId = config.tagId || null;
22
+
23
+ // Keep master behavior (prefer configured tagId), then fallback to UI tag/topic lookup.
24
+ if (!tagId && !tagRef) {
25
+ node.error("No tag provided: set Tag in the node OR provide msg.topic", msg);
26
+ return done && done();
27
+ }
28
+
29
+ if (!tagId) {
30
+ tagId = fuxa.getTagId(tagRef, null);
18
31
  }
32
+
33
+ if (!tagId) {
34
+ node.error("Tag not found: " + tagRef, msg);
35
+ return done && done();
36
+ }
37
+
38
+ const value = fuxa.getTag(tagId);
39
+
40
+ msg.payload = value;
41
+ if (tagRef) {
42
+ msg.topic = tagRef;
43
+ } else if (typeof config.tag === "string" && config.tag.trim()) {
44
+ msg.topic = config.tag.trim();
45
+ }
46
+
47
+ send(msg);
48
+ return done && done();
19
49
  } catch (err) {
20
50
  node.error(err, msg);
51
+ return done && done(err);
21
52
  }
22
53
  });
23
54
  }
55
+
24
56
  RED.nodes.registerType("get-tag", FuxaGetTagNode);
25
- }
57
+ };