@frangoteam/fuxa-min 1.3.0 → 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 (35) hide show
  1. package/api/command/index.js +1 -1
  2. package/api/index.js +3 -0
  3. package/api/scripts/index.js +7 -3
  4. package/dist/assets/i18n/en.json +10 -3
  5. package/dist/assets/i18n/fr.json +2 -1
  6. package/dist/assets/i18n/ja.json +2 -1
  7. package/dist/assets/i18n/sv.json +2 -1
  8. package/dist/assets/i18n/zh-cn.json +2 -1
  9. package/dist/assets/i18n/zh-tw.json +2 -1
  10. package/dist/assets/lib/svgeditor/fuxa-editor.min.js +17 -23
  11. package/dist/index.html +1 -1
  12. package/dist/{main.bafae830903d548e.js → main.72bdfed42c527918.js} +6 -6
  13. package/docs/openapi.yaml +3 -0
  14. package/integrations/node-red/index.js +4 -5
  15. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.html +39 -35
  16. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.js +43 -17
  17. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.html +42 -34
  18. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.js +32 -12
  19. package/main.js +3 -0
  20. package/package.json +1 -1
  21. package/runtime/alarms/alarmstorage.js +45 -30
  22. package/runtime/apikeys/apiKeysStorage.js +34 -22
  23. package/runtime/devices/odbc/index.js +9 -4
  24. package/runtime/devices/s7/index.js +2 -2
  25. package/runtime/notificator/notifystorage.js +34 -23
  26. package/runtime/project/index.js +16 -0
  27. package/runtime/project/prjstorage.js +78 -40
  28. package/runtime/scripts/index.js +6 -2
  29. package/runtime/storage/calculator.js +17 -13
  30. package/runtime/storage/influxdb/index.js +8 -3
  31. package/runtime/storage/questdb/index.js +1 -1
  32. package/runtime/storage/sqlite/index.js +11 -8
  33. package/runtime/storage/tdengine/index.js +24 -9
  34. package/runtime/users/usrstorage.js +65 -61
  35. package/settings.default.js +3 -0
package/docs/openapi.yaml CHANGED
@@ -652,6 +652,9 @@ paths:
652
652
  tags: [Scripts]
653
653
  summary: Run script
654
654
  security: [{ AccessToken: [] }, { ApiKey: [] }]
655
+ description: >
656
+ Runs a stored server-side script. Requests that set `params.script.test=true`
657
+ are limited to authenticated administrators.
655
658
  requestBody:
656
659
  required: true
657
660
  content:
@@ -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();
@@ -1,34 +1,32 @@
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:""}, // Keep: tag name for display and backward compatibility
8
- tagId: {value:""} // New: unique identifier (optional, for backward compatibility)
6
+ name: {value: ""},
7
+ tag: {value: ""},
8
+ tagId: {value: ""}
9
9
  },
10
- inputs:1,
11
- outputs:1,
10
+ inputs: 1,
11
+ outputs: 1,
12
12
  icon: "white-globe.png",
13
13
  label: function() {
14
14
  if (this.name) {
15
15
  return this.name;
16
16
  }
17
- // Display tag name or tagId
18
17
  return this.tag || this.tagId || "get tag";
19
18
  },
20
19
  oneditprepare: function() {
21
20
  var node = this;
22
- var tagMap = {}; // Use tag.id as key to avoid tag.name duplication issues
23
-
21
+ var tagMap = {};
22
+
24
23
  $.getJSON('/nodered/fuxa/devices', function(data) {
25
24
  var datalist = $('#fuxa-tags');
26
25
  datalist.empty();
27
-
28
- data.forEach(function(device) {
29
- device.tags.forEach(function(tag) {
26
+
27
+ (data || []).forEach(function(device) {
28
+ (device.tags || []).forEach(function(tag) {
30
29
  var fullName = device.name + ' - ' + tag.name;
31
- // datalist option value format: tag.name(tag.id)
32
30
  var optionValue = tag.name + '(' + tag.id + ')';
33
31
  datalist.append('<option value="' + optionValue + '">' + fullName + '</option>');
34
32
  tagMap[tag.id] = {
@@ -38,39 +36,34 @@
38
36
  };
39
37
  });
40
38
  });
41
-
42
- // Listen for tagSelected input changes
39
+
43
40
  $('#node-input-tagSelected').on('change', function() {
44
41
  var selectedValue = $(this).val();
45
- // Parse format: tag.name(tag.id)
46
42
  var match = selectedValue.match(/^(.+)\(([^)]+)\)$/);
47
43
  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);
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('');
54
49
  }
55
50
  });
56
-
57
- // Initialize display (when editing existing node)
51
+
58
52
  if (node.tagId && tagMap[node.tagId]) {
59
- // Has tagId, construct tagSelected value
60
53
  var tagName = node.tag || tagMap[node.tagId].name;
61
54
  $('#node-input-tagSelected').val(tagName + '(' + node.tagId + ')');
62
55
  } 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);
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);
69
60
  break;
70
61
  }
71
62
  }
72
63
  }
73
64
  });
65
+
66
+ $('#node-input-tagSelected').attr('placeholder', 'Optional - leave empty to use msg.topic');
74
67
  }
75
68
  });
76
69
  </script>
@@ -82,14 +75,25 @@
82
75
  </div>
83
76
  <div class="form-row">
84
77
  <label for="node-input-tagSelected"><i class="fa fa-tag"></i> Tag</label>
85
- <input type="text" id="node-input-tagSelected" list="fuxa-tags" placeholder="Select Tag">
78
+ <input type="text" id="node-input-tagSelected" list="fuxa-tags" placeholder="Optional - leave empty to use msg.topic">
86
79
  <datalist id="fuxa-tags"></datalist>
87
80
  </div>
88
81
  <input type="hidden" id="node-input-tag">
89
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>
90
88
  </script>
91
89
 
92
90
  <script type="text/x-red" data-help-name="get-tag">
93
- <p>Get the value of a FUXA tag.</p>
94
- <p>The tag value is set to <code>msg.payload</code>.</p>
95
- </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,31 +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
- // Prefer config.tagId, fallback to config.tag for backward compatibility
11
- var tagId = config.tagId;
12
- if (!tagId && config.tag) {
13
- // Backward compatibility: old nodes use tag.name, need to convert to tagId
14
- tagId = fuxa.getTagId(config.tag, null);
13
+ if (!fuxa) {
14
+ node.error("FUXA not available in functionGlobalContext", msg);
15
+ return done && done();
15
16
  }
16
-
17
- if (tagId) {
18
- var value = fuxa.getTag(tagId);
19
- msg.payload = value;
20
- msg.topic = config.tag; // Set topic to tag name for join operations
21
- node.send(msg);
22
- } else {
23
- node.error('Tag not found: ' + (config.tag || config.tagId), msg);
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();
24
27
  }
28
+
29
+ if (!tagId) {
30
+ tagId = fuxa.getTagId(tagRef, null);
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();
25
49
  } catch (err) {
26
50
  node.error(err, msg);
51
+ return done && done(err);
27
52
  }
28
53
  });
29
54
  }
55
+
30
56
  RED.nodes.registerType("get-tag", FuxaGetTagNode);
31
- }
57
+ };
@@ -1,34 +1,32 @@
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:""}, // Keep: tag name for display and backward compatibility
8
- tagId: {value:""} // New: unique identifier (optional, for backward compatibility)
6
+ name: {value: ""},
7
+ tag: {value: ""},
8
+ tagId: {value: ""}
9
9
  },
10
- inputs:1,
11
- outputs:1,
10
+ inputs: 1,
11
+ outputs: 1,
12
12
  icon: "white-globe.png",
13
13
  label: function() {
14
14
  if (this.name) {
15
15
  return this.name;
16
16
  }
17
- // Display tag name or tagId
18
17
  return this.tag || this.tagId || "set tag";
19
18
  },
20
19
  oneditprepare: function() {
21
20
  var node = this;
22
- var tagMap = {}; // Use tag.id as key to avoid tag.name duplication issues
23
-
21
+ var tagMap = {};
22
+
24
23
  $.getJSON('/nodered/fuxa/devices', function(data) {
25
24
  var datalist = $('#fuxa-tags-set');
26
25
  datalist.empty();
27
-
28
- data.forEach(function(device) {
29
- device.tags.forEach(function(tag) {
26
+
27
+ (data || []).forEach(function(device) {
28
+ (device.tags || []).forEach(function(tag) {
30
29
  var fullName = device.name + ' - ' + tag.name;
31
- // datalist option value format: tag.name(tag.id)
32
30
  var optionValue = tag.name + '(' + tag.id + ')';
33
31
  datalist.append('<option value="' + optionValue + '">' + fullName + '</option>');
34
32
  tagMap[tag.id] = {
@@ -38,39 +36,34 @@
38
36
  };
39
37
  });
40
38
  });
41
-
42
- // Listen for tagSelected input changes
39
+
43
40
  $('#node-input-tagSelected').on('change', function() {
44
41
  var selectedValue = $(this).val();
45
- // Parse format: tag.name(tag.id)
46
42
  var match = selectedValue.match(/^(.+)\(([^)]+)\)$/);
47
43
  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);
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('');
54
49
  }
55
50
  });
56
-
57
- // Initialize display (when editing existing node)
51
+
58
52
  if (node.tagId && tagMap[node.tagId]) {
59
- // Has tagId, construct tagSelected value
60
53
  var tagName = node.tag || tagMap[node.tagId].name;
61
54
  $('#node-input-tagSelected').val(tagName + '(' + node.tagId + ')');
62
55
  } 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);
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);
69
60
  break;
70
61
  }
71
62
  }
72
63
  }
73
64
  });
65
+
66
+ $('#node-input-tagSelected').attr('placeholder', 'Optional - leave empty to use msg.topic');
74
67
  }
75
68
  });
76
69
  </script>
@@ -80,15 +73,30 @@
80
73
  <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
81
74
  <input type="text" id="node-input-name" placeholder="Name">
82
75
  </div>
76
+
83
77
  <div class="form-row">
84
78
  <label for="node-input-tagSelected"><i class="fa fa-tag"></i> Tag</label>
85
- <input type="text" id="node-input-tagSelected" list="fuxa-tags-set" placeholder="Select Tag">
79
+ <input type="text" id="node-input-tagSelected" list="fuxa-tags-set" placeholder="Optional - leave empty to use msg.topic">
86
80
  <datalist id="fuxa-tags-set"></datalist>
87
81
  </div>
88
82
  <input type="hidden" id="node-input-tag">
89
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>
90
90
  </script>
91
91
 
92
92
  <script type="text/x-red" data-help-name="set-tag">
93
- <p>Set the value of a FUXA tag to <code>msg.payload</code>.</p>
94
- </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,29 +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
- // Prefer config.tagId, fallback to config.tag for backward compatibility
11
- var tagId = config.tagId;
12
- if (!tagId && config.tag) {
13
- // Backward compatibility: old nodes use tag.name, need to convert to tagId
14
- 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();
15
27
  }
16
-
28
+
29
+ if (!tagId) {
30
+ tagId = fuxa.getTagId(tagRef, null);
31
+ }
32
+
17
33
  if (tagId) {
18
34
  await fuxa.setTag(tagId, msg.payload);
19
- node.send(msg);
35
+ send(msg);
36
+ return done && done();
20
37
  } else {
21
- node.error('Tag not found: ' + (config.tag || config.tagId), msg);
38
+ node.error("Tag not found: " + tagRef, msg);
39
+ return done && done();
22
40
  }
23
41
  } catch (err) {
24
42
  node.error(err, msg);
43
+ return done && done(err);
25
44
  }
26
45
  });
27
46
  }
47
+
28
48
  RED.nodes.registerType("set-tag", FuxaSetTagNode);
29
- }
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frangoteam/fuxa-min",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Web-based Process Visualization (SCADA/HMI/Dashboard) software",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -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
  }
@@ -13,6 +13,18 @@ var settings // Application settings
13
13
  var logger; // Application logger
14
14
  var db_apikeys; // Database of apikeys
15
15
 
16
+ function _run(sql, params = []) {
17
+ return new Promise((resolve, reject) => {
18
+ db_apikeys.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
@@ -73,18 +85,18 @@ function getApiKeys() {
73
85
  */
74
86
  function setApiKeys(apiKeys) {
75
87
  return new Promise(async function (resolve, reject) {
76
- for (var i = 0; i < apiKeys.length; i++) {
77
- const apiKey = apiKeys[i];
78
- var value = JSON.stringify(apiKey).replace(/\'/g,"''");
79
- var sql = "INSERT OR REPLACE INTO apikeys (name, value) VALUES('" + apiKey.id + "','"+ value + "');";
80
- await db_apikeys.exec(sql, function (err) {
81
- if (err) {
82
- logger.error(`apiKeysStorage.set apikeys failed! ${err}`);
83
- reject();
84
- }
85
- });
88
+ try {
89
+ const sql = "INSERT OR REPLACE INTO apikeys (name, value) VALUES(?, ?)";
90
+ for (var i = 0; i < apiKeys.length; i++) {
91
+ const apiKey = apiKeys[i];
92
+ var value = JSON.stringify(apiKey);
93
+ await _run(sql, [apiKey.id, value]);
94
+ }
95
+ resolve();
96
+ } catch (err) {
97
+ logger.error(`apiKeysStorage.set apikeys failed! ${err}`);
98
+ reject();
86
99
  }
87
- resolve();
88
100
  });
89
101
  }
90
102
 
@@ -93,17 +105,17 @@ function setApiKeys(apiKeys) {
93
105
  */
94
106
  function removeApiKeys(apiKeys) {
95
107
  return new Promise(async function (resolve, reject) {
96
- for (var i = 0; i < apiKeys.length; i++) {
97
- const apiKey = apiKeys[i];
98
- var sql = "DELETE FROM apikeys WHERE name = '" + apiKey.id + "'";
99
- await db_apikeys.exec(sql, function (err) {
100
- if (err) {
101
- logger.error(`apiKeysStorage.remove apikeys failed! ${err}`);
102
- reject();
103
- }
104
- });
108
+ try {
109
+ const sql = "DELETE FROM apikeys WHERE name = ?";
110
+ for (var i = 0; i < apiKeys.length; i++) {
111
+ const apiKey = apiKeys[i];
112
+ await _run(sql, [apiKey.id]);
113
+ }
114
+ resolve();
115
+ } catch (err) {
116
+ logger.error(`apiKeysStorage.remove apikeys failed! ${err}`);
117
+ reject();
105
118
  }
106
- resolve();
107
119
  });
108
120
  }
109
121
 
@@ -122,4 +134,4 @@ module.exports = {
122
134
  getApiKeys: getApiKeys,
123
135
  setApiKeys: setApiKeys,
124
136
  removeApiKeys: removeApiKeys
125
- };
137
+ };