@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
@@ -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
  enabled: {value:true},
9
10
  interval: {value:1000},
10
11
  deadband: {value:0}
@@ -13,17 +14,65 @@
13
14
  outputs:1,
14
15
  icon: "white-globe.png",
15
16
  label: function() {
16
- return this.name||this.tag||"set tag daq settings";
17
+ if (this.name) {
18
+ return this.name;
19
+ }
20
+ // Display tag name or tagId
21
+ return this.tag || this.tagId || "set tag daq settings";
17
22
  },
18
23
  oneditprepare: function() {
24
+ var node = this;
25
+ var tagMap = {}; // Use tag.id as key to avoid tag.name duplication issues
26
+
19
27
  $.getJSON('/nodered/fuxa/devices', function(data) {
20
28
  var datalist = $('#fuxa-tags-set-daq-settings');
21
29
  datalist.empty();
30
+
22
31
  data.forEach(function(device) {
23
32
  device.tags.forEach(function(tag) {
24
- datalist.append('<option value="' + tag.name + '">' + device.name + ' - ' + tag.name + '</option>');
33
+ var fullName = device.name + ' - ' + tag.name;
34
+ // datalist option value format: tag.name(tag.id)
35
+ var optionValue = tag.name + '(' + tag.id + ')';
36
+ datalist.append('<option value="' + optionValue + '">' + fullName + '</option>');
37
+ tagMap[tag.id] = {
38
+ name: tag.name,
39
+ deviceName: device.name,
40
+ fullName: fullName
41
+ };
25
42
  });
26
43
  });
44
+
45
+ // Listen for tagSelected input changes
46
+ $('#node-input-tagSelected').on('change', function() {
47
+ var selectedValue = $(this).val();
48
+ // Parse format: tag.name(tag.id)
49
+ var match = selectedValue.match(/^(.+)\(([^)]+)\)$/);
50
+ if (match) {
51
+ var tagName = match[1];
52
+ var tagId = match[2];
53
+
54
+ // Set the actual stored fields
55
+ $('#node-input-tag').val(tagName);
56
+ $('#node-input-tagId').val(tagId);
57
+ }
58
+ });
59
+
60
+ // Initialize display (when editing existing node)
61
+ if (node.tagId && tagMap[node.tagId]) {
62
+ // Has tagId, construct tagSelected value
63
+ var tagName = node.tag || tagMap[node.tagId].name;
64
+ $('#node-input-tagSelected').val(tagName + '(' + node.tagId + ')');
65
+ } else if (node.tag && !node.tagId) {
66
+ // Old node: only has tag (tag.name), need to find corresponding tagId
67
+ // Note: if there are duplicate names, only the first match will be found
68
+ for (var tagId in tagMap) {
69
+ if (tagMap[tagId].name === node.tag) {
70
+ $('#node-input-tagSelected').val(node.tag + '(' + tagId + ')');
71
+ $('#node-input-tagId').val(tagId);
72
+ break;
73
+ }
74
+ }
75
+ }
27
76
  });
28
77
  }
29
78
  });
@@ -35,10 +84,12 @@
35
84
  <input type="text" id="node-input-name" placeholder="Name">
36
85
  </div>
37
86
  <div class="form-row">
38
- <label for="node-input-tag"><i class="fa fa-tag"></i> Tag</label>
39
- <input type="text" id="node-input-tag" list="fuxa-tags-set-daq-settings" placeholder="Tag Name">
87
+ <label for="node-input-tagSelected"><i class="fa fa-tag"></i> Tag</label>
88
+ <input type="text" id="node-input-tagSelected" list="fuxa-tags-set-daq-settings" placeholder="Select Tag">
40
89
  <datalist id="fuxa-tags-set-daq-settings"></datalist>
41
90
  </div>
91
+ <input type="hidden" id="node-input-tag">
92
+ <input type="hidden" id="node-input-tagId">
42
93
  <div class="form-row">
43
94
  <label for="node-input-enabled">&nbsp;</label>
44
95
  <input type="checkbox" id="node-input-enabled" style="display:inline-block; width:15px; vertical-align:baseline;">
@@ -6,29 +6,33 @@ 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 = {
14
- enabled: config.enabled,
15
- interval: config.interval,
16
- deadband: config.deadband
17
- };
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);
16
+ }
17
+ }
18
+
19
+ if (tagId) {
20
+ var settings = {
21
+ enabled: config.enabled,
22
+ interval: config.interval,
23
+ deadband: config.deadband
24
+ };
18
25
 
19
- // Override with message properties if provided
20
- if (msg.enabled !== undefined) settings.enabled = msg.enabled;
21
- if (msg.interval !== undefined) settings.interval = msg.interval;
22
- if (msg.deadband !== undefined) settings.deadband = msg.deadband;
26
+ // Override with message properties if provided
27
+ if (msg.enabled !== undefined) settings.enabled = msg.enabled;
28
+ if (msg.interval !== undefined) settings.interval = msg.interval;
29
+ if (msg.deadband !== undefined) settings.deadband = msg.deadband;
23
30
 
24
- var result = await fuxa.setTagDaqSettings(tagId, settings);
25
- msg.payload = result;
26
- node.send(msg);
27
- } else {
28
- node.error('Tag not found: ' + tagName, msg);
29
- }
31
+ var result = await fuxa.setTagDaqSettings(tagId, settings);
32
+ msg.payload = result;
33
+ node.send(msg);
30
34
  } else {
31
- node.error('Tag name not specified', msg);
35
+ node.error('Tag not found: ' + (config.tag || msg.tag || config.tagId), msg);
32
36
  }
33
37
  } catch (err) {
34
38
  node.error(err, msg);
@@ -1,27 +1,69 @@
1
1
  <script type="text/javascript">
2
- RED.nodes.registerType('set-tag',{
2
+ RED.nodes.registerType('set-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||"set tag";
14
+ if (this.name) {
15
+ return this.name;
16
+ }
17
+ return this.tag || this.tagId || "set 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-set');
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>
@@ -31,13 +73,30 @@
31
73
  <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
32
74
  <input type="text" id="node-input-name" placeholder="Name">
33
75
  </div>
76
+
34
77
  <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-set" placeholder="Tag Name">
78
+ <label for="node-input-tagSelected"><i class="fa fa-tag"></i> Tag</label>
79
+ <input type="text" id="node-input-tagSelected" list="fuxa-tags-set" placeholder="Optional - leave empty to use msg.topic">
37
80
  <datalist id="fuxa-tags-set"></datalist>
38
81
  </div>
82
+ <input type="hidden" id="node-input-tag">
83
+ <input type="hidden" id="node-input-tagId">
84
+
85
+ <div class="form-row">
86
+ <div style="margin-left: 105px; font-size: 12px; color: #666;">
87
+ If <b>Tag</b> is empty, the node uses <code>msg.topic</code> as the tag name/id.
88
+ </div>
89
+ </div>
39
90
  </script>
40
91
 
41
92
  <script type="text/x-red" data-help-name="set-tag">
42
- <p>Set the value of a FUXA tag to <code>msg.payload</code>.</p>
43
- </script>
93
+ <p>Sets the value of a FUXA tag to <code>msg.payload</code>.</p>
94
+ <p><b>Tag selection:</b></p>
95
+ <ul>
96
+ <li>If the node's <b>Tag</b> field is set, that tag is used.</li>
97
+ <li>If the node's <b>Tag</b> field is empty, the node uses <code>msg.topic</code> as the tag name/id.</li>
98
+ </ul>
99
+ <p><b>Example:</b></p>
100
+ <pre>msg.topic = "V2";
101
+ msg.payload = true;</pre>
102
+ </script>
@@ -1,23 +1,49 @@
1
1
  module.exports = function(RED) {
2
2
  function FuxaSetTagNode(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", async function(msg, send, done) {
10
+ send = send || node.send.bind(node);
7
11
 
8
- this.on('input', async function(msg) {
9
12
  try {
10
- var tagId = fuxa.getTagId(config.tag, null);
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);
31
+ }
32
+
11
33
  if (tagId) {
12
34
  await fuxa.setTag(tagId, msg.payload);
13
- node.send(msg);
35
+ send(msg);
36
+ return done && done();
14
37
  } else {
15
- node.error('Tag not found: ' + config.tag, msg);
38
+ node.error("Tag not found: " + tagRef, msg);
39
+ return done && done();
16
40
  }
17
41
  } catch (err) {
18
42
  node.error(err, msg);
43
+ return done && done(err);
19
44
  }
20
45
  });
21
46
  }
47
+
22
48
  RED.nodes.registerType("set-tag", FuxaSetTagNode);
23
- }
49
+ };
package/main.js CHANGED
@@ -133,6 +133,9 @@ try {
133
133
  if (mysettings.language) {
134
134
  settings.language = mysettings.language;
135
135
  }
136
+ if (!utils.isNullOrUndefined(mysettings.hideEditorOnboarding)) {
137
+ settings.hideEditorOnboarding = mysettings.hideEditorOnboarding;
138
+ }
136
139
  if (mysettings.uiPort) {
137
140
  settings.uiPort = mysettings.uiPort;
138
141
  }
@@ -148,6 +151,12 @@ try {
148
151
  if (mysettings.tokenExpiresIn) {
149
152
  settings.tokenExpiresIn = mysettings.tokenExpiresIn;
150
153
  }
154
+ if (!utils.isNullOrUndefined(mysettings.enableRefreshCookieAuth)) {
155
+ settings.enableRefreshCookieAuth = mysettings.enableRefreshCookieAuth;
156
+ }
157
+ if (mysettings.refreshTokenExpiresIn) {
158
+ settings.refreshTokenExpiresIn = mysettings.refreshTokenExpiresIn;
159
+ }
151
160
  if (mysettings.secretCode) {
152
161
  settings.secretCode = mysettings.secretCode;
153
162
  }
@@ -189,6 +198,12 @@ try {
189
198
  logger.error('Error loading user settings file: ' + userSettingsFile)
190
199
  }
191
200
 
201
+ // Ensure secure mode never runs with an empty/static-known JWT secret.
202
+ if (settings.secureEnabled && !settings.secretCode) {
203
+ settings.secretCode = utils.generateSecretCode();
204
+ logger.warn('Generated a random JWT secret in memory because secureEnabled=true and secretCode was missing. Persist it in settings for stable sessions across restarts.');
205
+ }
206
+
192
207
  // Check logger
193
208
  if (!settings.logDir) {
194
209
  settings.logDir = path.resolve(rootDir, '_logs');
@@ -260,6 +275,7 @@ var www = path.resolve(__dirname, '../client/dist');
260
275
  if (!fs.existsSync(www)) { // compatibility with docker/npm/electron
261
276
  www = path.resolve(__dirname, './dist');
262
277
  }
278
+
263
279
  settings.httpStatic = settings.httpStatic || www;
264
280
 
265
281
  if (parsedArgs.port !== undefined) {
@@ -321,10 +337,13 @@ const allowCrossDomain = function (req, res, next) {
321
337
 
322
338
  if (isOriginAllowed(origin)) {
323
339
  res.header('Access-Control-Allow-Origin', origin || '*');
340
+ if (settings.enableRefreshCookieAuth) {
341
+ res.header('Access-Control-Allow-Credentials', 'true');
342
+ }
324
343
  }
325
344
 
326
345
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
327
- res.header('Access-Control-Allow-Headers', 'x-access-token, x-auth-user, Origin, Content-Type, Accept');
346
+ res.header('Access-Control-Allow-Headers', 'x-access-token, x-auth-user, Origin, Content-Type, Accept, Skip-Auth, Skip-Error');
328
347
 
329
348
 
330
349
  if (req.method === 'OPTIONS') {
@@ -348,22 +367,24 @@ app.use('/_widgets', express.static(settings.widgetsFileDir));
348
367
  app.use('/snapshots', express.static(settings.webcamSnapShotsDir))
349
368
 
350
369
  var accessLogStream = fs.createWriteStream(settings.logDir + '/api.log', { flags: 'a' });
351
- app.use(morgan('combined', {
352
- stream: accessLogStream,
353
- skip: function (req, res) { return res.statusCode < 400 }
354
- }));
355
-
356
- app.use(morgan('dev', {
357
- skip: function (req, res) {
358
- return res.statusCode < 400
359
- }, stream: process.stderr
360
- }));
361
-
362
- app.use(morgan('dev', {
363
- skip: function (req, res) {
364
- return res.statusCode >= 400
365
- }, stream: process.stdout
366
- }));
370
+ if (runtime.settings.logApiLevel !== 'none') {
371
+ app.use(morgan('combined', {
372
+ stream: accessLogStream,
373
+ skip: function (req, res) { return res.statusCode < 400 }
374
+ }));
375
+
376
+ app.use(morgan('dev', {
377
+ skip: function (req, res) {
378
+ return res.statusCode < 400
379
+ }, stream: process.stderr
380
+ }));
381
+
382
+ app.use(morgan('dev', {
383
+ skip: function (req, res) {
384
+ return res.statusCode >= 400
385
+ }, stream: process.stdout
386
+ }));
387
+ }
367
388
 
368
389
  function mountSwaggerIfEnabled() {
369
390
  const swaggerEnabled = settings.swagger || settings.swaggerEnabled;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frangoteam/fuxa-min",
3
- "version": "1.2.11",
3
+ "version": "1.3.1",
4
4
  "description": "Web-based Process Visualization (SCADA/HMI/Dashboard) software",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -36,12 +36,13 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@influxdata/influxdb-client": "1.25.0",
39
+ "@questdb/nodejs-client": "3.0.0",
39
40
  "@tdengine/rest": "3.0.2",
40
41
  "ads-client": "2.1.0",
41
42
  "app-root-path": "2.2.1",
42
43
  "async": "3.2.3",
43
44
  "async-mutex": "0.5.0",
44
- "axios": "0.30.2",
45
+ "axios": "1.13.5",
45
46
  "bcryptjs": "2.4.3",
46
47
  "bluebird": "3.7.2",
47
48
  "body-parser": "1.20.3",
@@ -52,19 +53,20 @@
52
53
  "fontkit": "^2.0.2",
53
54
  "fs-extra": "7.0.1",
54
55
  "influx": "5.9.3",
55
- "ip": "2.0.1",
56
+ "ip": "~2.0.1",
56
57
  "jsonwebtoken": "9.0.0",
57
58
  "live-plugin-manager": "0.15.1",
58
59
  "modbus-serial": "8.0.19",
59
- "morgan": "1.10.0",
60
+ "morgan": "1.10.1",
60
61
  "mqtt": "4.3.7",
61
62
  "node-bacnet": "0.2.4",
62
63
  "node-opcua": "2.149.0",
63
64
  "node-red": "^4.1.0",
64
65
  "node-schedule": "2.1.1",
65
- "nodemailer": "7.0.7",
66
+ "nodemailer": "7.0.13",
66
67
  "nopt": "5.0.0",
67
68
  "pdfmake": "0.2.5",
69
+ "pg": "8.18.0",
68
70
  "socket.io": "4.8.1",
69
71
  "sqlite3": "5.1.5",
70
72
  "swagger-ui-express": "^5.0.1",
@@ -72,6 +74,9 @@
72
74
  "ws": "8.18.0",
73
75
  "yamljs": "^0.3.0"
74
76
  },
77
+ "overrides": {
78
+ "tar": "^7.4.3"
79
+ },
75
80
  "devDependencies": {
76
81
  "@mapbox/node-pre-gyp": "^1.0.0",
77
82
  "@types/express": "5.0.0",
@@ -13,6 +13,18 @@ var settings // Application settings
13
13
  var logger; // Application logger
14
14
  var db_alarms; // Database of alarms
15
15
 
16
+ function _run(sql, params = []) {
17
+ return new Promise((resolve, reject) => {
18
+ db_alarms.run(sql, params, function (err) {
19
+ if (err) {
20
+ reject(err);
21
+ } else {
22
+ resolve(this);
23
+ }
24
+ });
25
+ });
26
+ }
27
+
16
28
  /**
17
29
  * Init and bind the database resource
18
30
  * @param {*} _settings
@@ -135,33 +147,38 @@ function getAlarms() {
135
147
  * Set alarm value in database
136
148
  */
137
149
  function setAlarms(alarms) {
138
- return new Promise(function (resolve, reject) {
139
- // prepare query
140
- if (alarms && alarms.length) {
141
- var sql = "";
142
- alarms.forEach(alr => {
150
+ return new Promise(async function (resolve, reject) {
151
+ if (!alarms || !alarms.length) {
152
+ resolve();
153
+ return;
154
+ }
155
+ try {
156
+ await _run('BEGIN TRANSACTION');
157
+ for (const alr of alarms) {
143
158
  let grp = alr.subproperty.group || '';
144
159
  let status = alr.status || '';
145
160
  let userack = alr.userack || '';
146
- //is alarm condition is changed (if it is occured or acknowledged) insert or update record
147
- sql += "INSERT OR REPLACE INTO alarms (nametype, type, status, ontime, offtime, acktime) VALUES('" +
148
- alr.getId() + "','" + alr.type + "','" + status + "','" + alr.ontime + "','" + alr.offtime + "','" + alr.acktime + "');" +
149
- "INSERT OR REPLACE INTO chronicle (Sn, nametype, type, status, text, grp, ontime, offtime, acktime, userack)" +
150
- " VALUES ((SELECT Sn from chronicle WHERE ontime='" + alr.ontime + "' AND nametype='" + alr.getId() + "'),'" +
151
- alr.getId() + "','" + alr.type + "','" + status + "','" + alr.subproperty.text + "','" + grp + "','" + alr.ontime + "','" + alr.offtime + "','" + alr.acktime + "','" + userack + "');";
161
+ const alarmId = alr.getId();
162
+ await _run(
163
+ "INSERT OR REPLACE INTO alarms (nametype, type, status, ontime, offtime, acktime) VALUES(?, ?, ?, ?, ?, ?)",
164
+ [alarmId, alr.type, status, alr.ontime, alr.offtime, alr.acktime]
165
+ );
166
+ await _run(
167
+ "INSERT OR REPLACE INTO chronicle (Sn, nametype, type, status, text, grp, ontime, offtime, acktime, userack) VALUES ((SELECT Sn from chronicle WHERE ontime = ? AND nametype = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?)",
168
+ [alr.ontime, alarmId, alarmId, alr.type, status, alr.subproperty.text, grp, alr.ontime, alr.offtime, alr.acktime, userack]
169
+ );
152
170
  if (alr.toremove) {
153
- //is alarm to be removed (if it is ok) delete it from db
154
- sql += "DELETE FROM alarms WHERE nametype = '" + alr.getId() + "';";
171
+ await _run("DELETE FROM alarms WHERE nametype = ?", [alarmId]);
155
172
  }
156
- });
157
- db_alarms.exec(sql, function (err) {
158
- if (err) {
159
- logger.error('alarmsstorage.failed-to-set: ' + err);
160
- reject();
161
- } else {
162
- resolve();
163
- }
164
- });
173
+ }
174
+ await _run('COMMIT');
175
+ resolve();
176
+ } catch (err) {
177
+ try {
178
+ await _run('ROLLBACK');
179
+ } catch (_) {}
180
+ logger.error('alarmsstorage.failed-to-set: ' + err);
181
+ reject();
165
182
  }
166
183
  });
167
184
  }
@@ -172,14 +189,12 @@ function setAlarms(alarms) {
172
189
  function removeAlarm(alarm) {
173
190
  return new Promise(function (resolve, reject) {
174
191
  // prepare query
175
- var sql = "DELETE FROM alarms WHERE nametype = '" + alarm.getId() + "'";
176
- db_alarms.exec(sql, function (err) {
177
- if (err) {
178
- logger.error('alarmsstorage.failed-to-remove: ' + err);
179
- reject();
180
- } else {
181
- resolve();
182
- }
192
+ var sql = "DELETE FROM alarms WHERE nametype = ?";
193
+ _run(sql, [alarm.getId()]).then(function () {
194
+ resolve();
195
+ }).catch(function (err) {
196
+ logger.error('alarmsstorage.failed-to-remove: ' + err);
197
+ reject();
183
198
  });
184
199
  });
185
200
  }