@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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Calculator for DAQ value array (integrale, max, min, average...)
2
+ * Calculator for DAQ value array (integrale, max, min, average...)
3
3
  */
4
4
 
5
5
  'use strict';
@@ -19,11 +19,11 @@ function getFunctionValues(values, fromts, tots, fnc, interval, format) {
19
19
  }
20
20
 
21
21
  /**
22
- *
23
- * @param {*} values
24
- * @param {*} fnc
25
- * @param {*} intervalType hour / day
26
- * @returns
22
+ *
23
+ * @param {*} values
24
+ * @param {*} fnc
25
+ * @param {*} intervalType hour / day
26
+ * @returns
27
27
  */
28
28
  function getMin(timeserie, fromts, tots, intervalType) {
29
29
  let result = getInterval(fromts, tots, intervalType, Number.MAX_VALUE);
@@ -39,7 +39,7 @@ function getMin(timeserie, fromts, tots, intervalType) {
39
39
  intervals[intervalIndex] = value;
40
40
  }
41
41
  }
42
-
42
+
43
43
  for (let i = 0; i < sorted.length; i++) {
44
44
  let intervalIndex = getIntervalTime(sorted[i].dt, intervalType, false).getTime();
45
45
  addToInterval(result, intervalIndex, utils.parseFloat(sorted[i].value, 5));
@@ -61,7 +61,7 @@ function getMax(timeserie, fromts, tots, intervalType) {
61
61
  intervals[intervalIndex] = value;
62
62
  }
63
63
  }
64
-
64
+
65
65
  for (let i = 0; i < sorted.length; i++) {
66
66
  let intervalIndex = getIntervalTime(sorted[i].dt, intervalType, false).getTime();
67
67
  addToInterval(result, intervalIndex, utils.parseFloat(sorted[i].value, 5));
@@ -85,7 +85,7 @@ function getAverage(timeserie, fromts, tots, intervalType, format) {
85
85
  }
86
86
  counters[intervalIndex]++;
87
87
  }
88
-
88
+
89
89
  for (let i = 0; i < sorted.length; i++) {
90
90
  let intervalIndex = getIntervalTime(sorted[i].dt, intervalType, false).getTime();
91
91
  addToInterval(result, counts, intervalIndex, parseFloat(sorted[i].value));
@@ -96,7 +96,7 @@ function getAverage(timeserie, fromts, tots, intervalType, format) {
96
96
  result[k] = utils.parseFloat(result[k] / counts[k], format || 5);
97
97
  }
98
98
  });
99
- return result;
99
+ return result;
100
100
  }
101
101
 
102
102
  function getSum(timeserie, fromts, tots, intervalType) {
@@ -114,12 +114,12 @@ function getSum(timeserie, fromts, tots, intervalType) {
114
114
  intervals[intervalIndex] = utils.parseFloat(intervals[intervalIndex], 5)
115
115
  }
116
116
  }
117
-
117
+
118
118
  for (let i = 0; i < sorted.length; i++) {
119
119
  let intervalIndex = getIntervalTime(sorted[i].dt, intervalType, false).getTime();
120
120
  addToInterval(result, intervalIndex, parseFloat(sorted[i].value));
121
121
  }
122
- return result;
122
+ return result;
123
123
  }
124
124
 
125
125
 
@@ -137,7 +137,7 @@ function getIntegral(timeserie, fromts, tots, intervalType) {
137
137
  intervals[intervalIndex] += value;
138
138
  }
139
139
  }
140
-
140
+
141
141
  let lastRecord = null;// : TimeValue { dt: number, value: number };
142
142
  let lastIntervalIndex = null;
143
143
  for (let i = 0; i < sorted.length; i++) {
@@ -191,6 +191,10 @@ function getStepDate(ts, type, toadd) {
191
191
  dtRange = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), dt.getHours(), 0, 0);
192
192
  var minutesRange = parseInt(dt.getMinutes() / 30);
193
193
  dtRange.setMinutes((minutesRange + toadd) * 30);
194
+ } else if (type === ReportIntervalType.min15) {
195
+ dtRange = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), dt.getHours(), 0, 0);
196
+ var minutesRange = parseInt(dt.getMinutes() / 15);
197
+ dtRange.setMinutes((minutesRange + toadd) * 15);
194
198
  } else if (type === ReportIntervalType.min10) {
195
199
  dtRange = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), dt.getHours(), 0, 0);
196
200
  var minutesRange = parseInt(dt.getMinutes() / 10);
@@ -21,6 +21,7 @@ var logger;
21
21
  var daqDB = {}; // list of daqDB node: SQlite one pro device, influxDB only one
22
22
  var currentStorateDB;
23
23
  var runtime;
24
+ var questDbModule = null;
24
25
 
25
26
  function init(_settings, _log, _runtime) {
26
27
  settings = _settings;
@@ -42,7 +43,7 @@ function reset() {
42
43
  function addDaqNode(_id, fncgetprop) {
43
44
  var id = _id;
44
45
  const dbType = _getDbType();
45
- if (dbType === DaqStoreTypeEnum.influxDB || dbType === DaqStoreTypeEnum.influxDB18 || dbType === DaqStoreTypeEnum.TDengine) {
46
+ if (dbType === DaqStoreTypeEnum.influxDB || dbType === DaqStoreTypeEnum.influxDB18 || dbType === DaqStoreTypeEnum.TDengine || dbType === DaqStoreTypeEnum.questDB) {
46
47
  id = dbType;
47
48
  }
48
49
  if (!daqDB[id]) {
@@ -50,6 +51,14 @@ function addDaqNode(_id, fncgetprop) {
50
51
  daqDB[id] = InfluxDB.create(settings, logger, currentStorateDB);
51
52
  } else if(id === DaqStoreTypeEnum.TDengine){
52
53
  daqDB[id] = TDengine.create(settings, logger, currentStorateDB);
54
+ } else if(id === DaqStoreTypeEnum.questDB){
55
+ const QuestDB = _getQuestDbModule();
56
+ if (QuestDB) {
57
+ daqDB[id] = QuestDB.create(settings, logger, currentStorateDB);
58
+ } else {
59
+ logger.warn('daqstorage: QuestDB storage selected but package dependencies are not installed. Falling back to SQLite.');
60
+ daqDB[id] = SqliteDB.create(settings, logger, id, currentStorateDB);
61
+ }
53
62
  } else {
54
63
  daqDB[id] = SqliteDB.create(settings, logger, id, currentStorateDB);
55
64
  }
@@ -160,16 +169,33 @@ function _getDaqNode(tagid) {
160
169
 
161
170
  function _getDbType() {
162
171
  if (settings.daqstore && settings.daqstore.type) {
172
+ if (settings.daqstore.type === 'QuestDB') {
173
+ return DaqStoreTypeEnum.questDB;
174
+ }
163
175
  return settings.daqstore.type;
164
176
  }
165
177
  return DaqStoreTypeEnum.SQlite;
166
178
  }
167
179
 
180
+ function _getQuestDbModule() {
181
+ if (questDbModule) {
182
+ return questDbModule;
183
+ }
184
+ try {
185
+ questDbModule = require("./questdb");
186
+ } catch (err) {
187
+ questDbModule = null;
188
+ logger.warn(`daqstorage: QuestDB module unavailable (${err.message})`);
189
+ }
190
+ return questDbModule;
191
+ }
192
+
168
193
  var DaqStoreTypeEnum = {
169
194
  SQlite: 'SQlite',
170
195
  influxDB: 'influxDB',
171
196
  influxDB18: 'influxDB18',
172
197
  TDengine: 'TDengine',
198
+ questDB: 'questDB',
173
199
  }
174
200
 
175
201
  function _getValue(value) {
@@ -187,4 +213,4 @@ module.exports = {
187
213
  getNodesValues: getNodesValues,
188
214
  checkRetention: checkRetention,
189
215
  getCurrentStorageFnc: getCurrentStorageFnc,
190
- };
216
+ };
@@ -12,6 +12,10 @@ var { InfluxDB, Point, flux } = require('@influxdata/influxdb-client');
12
12
  const VERSION_18_FLUX = '1.8-flux';
13
13
  const VERSION_20 = '2.0';
14
14
 
15
+ function escapeInfluxIdentifier(value) {
16
+ return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
17
+ }
18
+
15
19
  function Influx(_settings, _log, _currentStorate) {
16
20
 
17
21
  var settings = _settings; // Application settings
@@ -157,7 +161,8 @@ function Influx(_settings, _log, _currentStorate) {
157
161
  if (influxdbVersion === VERSION_18_FLUX) {
158
162
  fromts *= 1000000;
159
163
  tots *= 1000000;
160
- const query = `SELECT * FROM "${tagid}" WHERE time >= ${fromts} AND time <= ${tots}`;
164
+ const measurement = escapeInfluxIdentifier(tagid);
165
+ const query = `SELECT * FROM "${measurement}" WHERE time >= ${Number(fromts)} AND time <= ${Number(tots)}`;
161
166
  client.query(query).then((result) => {
162
167
  resolve(result.map(row => {
163
168
  return {
@@ -171,7 +176,7 @@ function Influx(_settings, _log, _currentStorate) {
171
176
  reject(error);
172
177
  });
173
178
  } else {
174
- const query = flux`from(bucket: "${settings.daqstore.bucket}") |> range(start: ${new Date(fromts)}, stop: ${new Date(tots)}) |> filter(fn: (r) => r.id == "${tagid}")`;
179
+ const query = flux`from(bucket: ${settings.daqstore.bucket}) |> range(start: ${new Date(fromts)}, stop: ${new Date(tots)}) |> filter(fn: (r) => r.id == ${String(tagid)})`;
175
180
  var result = [];
176
181
  queryApi.queryRows(query, {
177
182
  next(row, tableMeta) {
@@ -234,4 +239,4 @@ module.exports = {
234
239
  var InfluxDBStatusEnum = {
235
240
  OPEN: 'open',
236
241
  CLOSE: 'close',
237
- }
242
+ }
@@ -0,0 +1,224 @@
1
+ 'use strict'
2
+
3
+ const { Pool } = require('pg');
4
+ const { Sender } = require('@questdb/nodejs-client');
5
+ let utils = require('../../utils');
6
+
7
+ function QuestDB(_settings, _log, _currentStorage) {
8
+ let settings = _settings; // Application settings
9
+ const logger = _log; // Application logger
10
+ const currentStorage = _currentStorage; // Database to set the last value (current)
11
+ let pool = null;
12
+ let sender = null;
13
+ const tableName = getTableName();
14
+ let writeQueue = Promise.resolve();
15
+ let initPromise = Promise.resolve();
16
+
17
+ this.setCall = function (_fncGetProp) {
18
+ fncGetTagProp = _fncGetProp;
19
+ return this.addDaqValues;
20
+ }
21
+ var fncGetTagProp = null;
22
+
23
+ this.init = async function () {
24
+ try {
25
+ pool = new Pool(getQueryClientConfig());
26
+ sender = await Sender.fromConfig(getIngestConfigString());
27
+ await ensureSchema();
28
+ logger.info('QuestDB connected');
29
+ } catch (error) {
30
+ logger.error(`questdb-init failed! ${error}`);
31
+ }
32
+ }
33
+
34
+ this.addDaqValues = function (tagsValues, deviceName, deviceId) {
35
+ var dataToRestore = [];
36
+ var rowsToWrite = [];
37
+
38
+ for (const tagid in tagsValues) {
39
+ let tag = tagsValues[tagid];
40
+ if (!tag.daq || utils.isNullOrUndefined(tag.value) || Number.isNaN(tag.value)) {
41
+ if (tag.daq && tag.daq.restored) {
42
+ dataToRestore.push({ id: tag.id, deviceId: deviceId, value: tag.value });
43
+ }
44
+ if (tag.daq && !tag.daq.enabled) {
45
+ continue;
46
+ }
47
+ }
48
+
49
+ rowsToWrite.push({
50
+ tagid,
51
+ deviceId,
52
+ deviceName: deviceName || '',
53
+ unsPath: normalizeUnsPath(tag.unsPath),
54
+ value: tag.value,
55
+ timestamp: tag.timestamp || Date.now(),
56
+ });
57
+ }
58
+
59
+ if (rowsToWrite.length) {
60
+ writeQueue = writeQueue.then(async () => {
61
+ await initPromise;
62
+ if (!sender) {
63
+ return;
64
+ }
65
+
66
+ for (const row of rowsToWrite) {
67
+ const parsedValue = normalizeValue(row.value);
68
+ let line = sender
69
+ .table(tableName)
70
+ .symbol('tag_id', row.tagid)
71
+ .symbol('device_id', row.deviceId)
72
+ .stringColumn('device_name', row.deviceName);
73
+ if (!utils.isNullOrUndefined(row.unsPath)) {
74
+ line = line.symbol('uns_path', row.unsPath);
75
+ }
76
+
77
+ if (!utils.isNullOrUndefined(parsedValue.numberValue)) {
78
+ line = line.floatColumn('number_value', parsedValue.numberValue);
79
+ }
80
+ if (!utils.isNullOrUndefined(parsedValue.stringValue)) {
81
+ line = line.stringColumn('string_value', parsedValue.stringValue);
82
+ }
83
+ await line.at(Number(row.timestamp), 'ms');
84
+ }
85
+ await sender.flush();
86
+ }).catch((error) => {
87
+ logger.error(`questdb-addDaqValues failed! ${error}`);
88
+ });
89
+ }
90
+
91
+ if (dataToRestore.length && currentStorage) {
92
+ currentStorage.setValues(dataToRestore);
93
+ }
94
+ }
95
+
96
+ this.getDaqValue = function (tagid, fromts, tots) {
97
+ return new Promise(function (resolve, reject) {
98
+ initPromise.then(() => {
99
+ if (!pool) {
100
+ resolve([]);
101
+ return;
102
+ }
103
+
104
+ const query = `SELECT timestamp, number_value, string_value
105
+ FROM ${tableName}
106
+ WHERE tag_id = $1
107
+ AND timestamp >= $2
108
+ AND timestamp < $3
109
+ ORDER BY timestamp`;
110
+ const params = [tagid, new Date(fromts), new Date(tots)];
111
+
112
+ pool.query(query, params)
113
+ .then((result) => {
114
+ let data = [];
115
+ result.rows.forEach((row) => {
116
+ const value = !utils.isNullOrUndefined(row.number_value) ? Number(row.number_value) : row.string_value;
117
+ data.push({ dt: new Date(row.timestamp).getTime(), value });
118
+ });
119
+ resolve(data)
120
+ })
121
+ .catch((error) => {
122
+ logger.error(`questdb-getDaqValue failed! ${error}`)
123
+ reject(error)
124
+ })
125
+ }).catch((error) => {
126
+ logger.error(`questdb-getDaqValue failed! ${error}`)
127
+ reject(error)
128
+ });
129
+ })
130
+ }
131
+
132
+ this.close = function () {
133
+ if (sender) {
134
+ sender.close().catch((error) => {
135
+ logger.error(`questdb-close sender failed! ${error}`);
136
+ });
137
+ sender = null;
138
+ }
139
+ if (pool) {
140
+ pool.end().catch((error) => {
141
+ logger.error(`questdb-close pool failed! ${error}`);
142
+ });
143
+ pool = null;
144
+ }
145
+ }
146
+
147
+ this.getDaqMap = function (tagid) {
148
+ var dummy = {};
149
+ dummy[tagid] = true;
150
+ return dummy;
151
+ }
152
+
153
+ async function ensureSchema() {
154
+ if (!pool) {
155
+ return;
156
+ }
157
+ await pool.query(`CREATE TABLE IF NOT EXISTS ${tableName} (
158
+ timestamp TIMESTAMP,
159
+ tag_id SYMBOL,
160
+ number_value FLOAT,
161
+ string_value STRING,
162
+ device_id SYMBOL,
163
+ device_name STRING,
164
+ uns_path SYMBOL
165
+ ) TIMESTAMP(timestamp) PARTITION BY DAY`);
166
+ }
167
+
168
+ function getIngestConfigString() {
169
+ return settings.daqstore.configurationString || 'http::addr=localhost:9000;';
170
+ }
171
+
172
+ function getQueryClientConfig() {
173
+ return {
174
+ host: settings.daqstore.host || '127.0.0.1',
175
+ port: 8812, // Standard port
176
+ database: 'qdb', // Standard database
177
+ user: settings.daqstore.credentials?.username || 'admin',
178
+ password: settings.daqstore.credentials?.password || 'quest',
179
+ max: 10, // Pool with 10 connections
180
+ idleTimeoutMillis: 30000, // 30s
181
+ };
182
+ }
183
+
184
+ function getTableName() {
185
+ const name = (settings.daqstore.tableName || 'meters').trim();
186
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
187
+ return name.toLowerCase();
188
+ }
189
+ logger.warn(`questdb invalid tableName "${name}", fallback to meters`);
190
+ return 'meters';
191
+ }
192
+
193
+ function normalizeValue(value) {
194
+ if (utils.isNullOrUndefined(value)) {
195
+ return { numberValue: null, stringValue: null };
196
+ }
197
+ if (utils.isBoolean(value)) {
198
+ return {
199
+ numberValue: value ? 1 : 0,
200
+ stringValue: null
201
+ };
202
+ }
203
+ if (typeof value === 'number' && Number.isFinite(value)) {
204
+ return { numberValue: value, stringValue: null };
205
+ }
206
+ return { numberValue: null, stringValue: String(value) };
207
+ }
208
+
209
+ function normalizeUnsPath(value) {
210
+ if (utils.isNullOrUndefined(value)) {
211
+ return null;
212
+ }
213
+ const normalized = String(value).trim();
214
+ return normalized.length ? normalized : null;
215
+ }
216
+
217
+ initPromise = this.init();
218
+ }
219
+
220
+ module.exports = {
221
+ create: function (data, logger, currentStorage) {
222
+ return new QuestDB(data, logger, currentStorage);
223
+ }
224
+ };
@@ -227,14 +227,14 @@ function DaqNode(_settings, _log, _id, _currentStorate) {
227
227
  _checkDataWorking(false);
228
228
  });
229
229
  }
230
- // check function to add daq data
230
+ // check function to add daq data
231
231
  if (addDaqfnc.length > 0) {
232
232
  if (_checkDataWorking(true)) {
233
233
  Promise.all(addDaqfnc).then(result => {
234
234
  // check db tokenizer after inserted
235
235
  var lastts = 0 || result[0];
236
236
  if (daqNextToken && daqNextToken < lastts) {
237
- // close data DB, open and bind the new db, rename and move the closed db
237
+ // close data DB, open and bind the new db, rename and move the closed db
238
238
  db_daqdata.close(function () {
239
239
  var suffix = _getDateTimeSuffix(new Date());
240
240
  var oldfile = db_daqdata_file;
@@ -262,6 +262,9 @@ function DaqNode(_settings, _log, _id, _currentStorate) {
262
262
  }
263
263
  _checkDataWorking(false);
264
264
  });
265
+ } else {
266
+ // overload: attach no-op catch handlers to prevent UnhandledPromiseRejection
267
+ addDaqfnc.forEach(function (p) { p.catch(function () {}); });
265
268
  }
266
269
  }
267
270
  if (dataToRestore.length && currentStorage) {
@@ -554,8 +557,8 @@ function DaqNode(_settings, _log, _id, _currentStorate) {
554
557
 
555
558
  function _insertTagToMap(tagid, name, type) {
556
559
  return new Promise(function (resolve, reject) {
557
- var sqlRequest = "INSERT INTO data (id, name, type) ";
558
- db_daqmap.run(sqlRequest + "VALUES('" + tagid + "','" + name + "','" + type + "'); SELECT last_insert_rowid() FROM data", function (err) {
560
+ var sqlRequest = "INSERT INTO data (id, name, type) VALUES (?, ?, ?)";
561
+ db_daqmap.run(sqlRequest, [tagid, name, type], function (err) {
559
562
  if (err !== null) {
560
563
  reject(err);
561
564
  } else {
@@ -615,10 +618,10 @@ function DaqNode(_settings, _log, _id, _currentStorate) {
615
618
  function _insertTagValue(mapid, value) {
616
619
  return new Promise(function (resolve, reject) {
617
620
  var ts = new Date().getTime();
618
- var sqlRequest = "INSERT INTO data (dt, id, value) ";
619
- db_daqdata.run(sqlRequest + "VALUES('" + ts + "','" + mapid + "','" + value + "')", function (err) {
621
+ var sqlRequest = "INSERT INTO data (dt, id, value) VALUES (?, ?, ?)";
622
+ db_daqdata.run(sqlRequest, [ts, mapid, value], function (err) {
620
623
  if (err !== null) {
621
- reject();
624
+ reject(err);
622
625
  }
623
626
  else {
624
627
  resolve(ts);
@@ -692,4 +695,4 @@ module.exports = {
692
695
  return new DaqNode(data, logger, id, currentStorate);
693
696
  },
694
697
  checkRetention: checkRetention
695
- };
698
+ };
@@ -3,11 +3,20 @@
3
3
  let { options, connect } = require("@tdengine/rest");
4
4
  let utils = require('../../utils');
5
5
 
6
+ function quoteTdIdentifier(value) {
7
+ return '`' + String(value).replace(/`/g, '``') + '`';
8
+ }
9
+
10
+ function escapeTdString(value) {
11
+ return String(value).replace(/'/g, "''");
12
+ }
13
+
6
14
  function TDengine(_settings, _log, _currentStorage) {
7
15
  let settings = _settings; // Application settings
8
16
  const logger = _log; // Application logger
9
17
  const currentStorage = _currentStorage; // Database to set the last value (current)
10
18
  const database = settings.daqstore.database || 'fuxa';
19
+ const databaseRef = quoteTdIdentifier(database);
11
20
  let conn;
12
21
 
13
22
  this.setCall = function (_fncGetProp) {
@@ -25,8 +34,8 @@ function TDengine(_settings, _log, _currentStorage) {
25
34
  const cursor = conn.cursor();
26
35
  try {
27
36
  //TODO add retention
28
- let res = await cursor.query(`CREATE DATABASE IF NOT EXISTS ${database} `);
29
- res = await cursor.query(`CREATE STABLE IF NOT EXISTS ${database}.meters (dt TIMESTAMP,tag_id VARCHAR(200), tag_value BINARY(20)) TAGS (device_id VARCHAR(200),device_name BINARY(256) )`);
37
+ let res = await cursor.query(`CREATE DATABASE IF NOT EXISTS ${databaseRef} `);
38
+ res = await cursor.query(`CREATE STABLE IF NOT EXISTS ${databaseRef}.meters (dt TIMESTAMP,tag_id VARCHAR(200), tag_value BINARY(20)) TAGS (device_id VARCHAR(200),device_name BINARY(256) )`);
30
39
 
31
40
  } catch (error) {
32
41
  console.error(error);
@@ -47,8 +56,13 @@ function TDengine(_settings, _log, _currentStorage) {
47
56
  continue;
48
57
  }
49
58
  }
50
- let insertSql = `INSERT INTO ${database}.\`${deviceId}\` USING ${database}.meters TAGS('${deviceId}','${deviceName}')
51
- VALUES (NOW, '${tagid}', '${tag.value}')`;
59
+ const safeDeviceId = quoteTdIdentifier(deviceId);
60
+ const safeDeviceTag = escapeTdString(deviceId);
61
+ const safeDeviceName = escapeTdString(deviceName);
62
+ const safeTagId = escapeTdString(tagid);
63
+ const safeTagValue = escapeTdString(tag.value);
64
+ let insertSql = `INSERT INTO ${databaseRef}.${safeDeviceId} USING ${databaseRef}.meters TAGS('${safeDeviceTag}','${safeDeviceName}')
65
+ VALUES (NOW, '${safeTagId}', '${safeTagValue}')`;
52
66
  //async
53
67
 
54
68
  cursor.query(insertSql).then((rst) => {
@@ -71,11 +85,12 @@ function TDengine(_settings, _log, _currentStorage) {
71
85
  const cursor = conn.cursor()
72
86
  let data = []
73
87
  //add by J, the tagid is missed in the sql, should be one of the filter condition
88
+ const safeTagId = escapeTdString(tagid);
74
89
  cursor.query(`SELECT CAST(dt as BIGINT) as dt, tag_value
75
- FROM ${database}.meters
76
- WHERE tag_id = '${tagid}'
77
- and dt >= ${fromts}
78
- and dt < ${tots} `).then((result) => {
90
+ FROM ${databaseRef}.meters
91
+ WHERE tag_id = '${safeTagId}'
92
+ and dt >= ${Number(fromts)}
93
+ and dt < ${Number(tots)} `).then((result) => {
79
94
  // logger.debug(result)
80
95
  result.getData().forEach((row) => {
81
96
  data.push({ dt: row[0], value: row[1] })
@@ -105,4 +120,4 @@ module.exports = {
105
120
  create: function (data, logger, currentStorage) {
106
121
  return new TDengine(data, logger, currentStorage);
107
122
  }
108
- };
123
+ };