@frangoteam/fuxa-min 1.2.11 → 1.3.0

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 (50) hide show
  1. package/api/auth/index.js +141 -3
  2. package/api/index.js +18 -2
  3. package/api/jwt-helper.js +3 -1
  4. package/api/resources/index.js +19 -2
  5. package/dist/3rdpartylicenses.txt +139 -7
  6. package/dist/assets/i18n/de.json +10 -0
  7. package/dist/assets/i18n/en.json +14 -3
  8. package/dist/assets/i18n/es.json +12 -0
  9. package/dist/assets/i18n/fr.json +10 -0
  10. package/dist/assets/i18n/ja.json +15 -6
  11. package/dist/assets/i18n/ko.json +12 -0
  12. package/dist/assets/i18n/pt.json +9 -2
  13. package/dist/assets/i18n/ru.json +11 -0
  14. package/dist/assets/i18n/sv.json +10 -1
  15. package/dist/assets/i18n/tr.json +8 -1
  16. package/dist/assets/i18n/ua.json +9 -2
  17. package/dist/assets/i18n/zh-cn.json +10 -0
  18. package/dist/assets/i18n/zh-tw.json +11 -1
  19. package/dist/index.html +2 -2
  20. package/dist/main.bafae830903d548e.js +329 -0
  21. package/dist/polyfills.d7de05f9af2fb559.js +1 -0
  22. package/dist/{runtime.8ef63094e52a66ba.js → runtime.9136a61a9b98f987.js} +1 -1
  23. package/dist/{scripts.40b60f02658462e4.js → scripts.d9e6ee984bf6f3b7.js} +1 -1
  24. package/dist/styles.545e37beb3e671ba.css +1 -0
  25. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.html +56 -5
  26. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.js +8 -2
  27. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.html +56 -5
  28. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.js +12 -12
  29. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.html +56 -5
  30. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.js +14 -10
  31. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.html +56 -5
  32. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.js +8 -2
  33. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.html +56 -5
  34. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.js +24 -20
  35. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.html +56 -5
  36. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.js +8 -2
  37. package/main.js +35 -17
  38. package/package.json +10 -5
  39. package/runtime/devices/adsclient/index.js +1 -1
  40. package/runtime/devices/ethernetip/index.js +1 -1
  41. package/runtime/devices/gpio/index.js +1 -1
  42. package/runtime/devices/odbc/index.js +5 -5
  43. package/runtime/devices/template/index.js +14 -14
  44. package/runtime/storage/daqstorage.js +28 -2
  45. package/runtime/storage/questdb/index.js +224 -0
  46. package/runtime/utils.js +5 -0
  47. package/settings.default.js +5 -2
  48. package/dist/main.92522279642ef880.js +0 -329
  49. package/dist/polyfills.c8e7db9850a3ad8b.js +0 -1
  50. package/dist/styles.03cc550382689976.css +0 -1
@@ -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.stringColumn('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
+ };
package/runtime/utils.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const os = require('os');
2
2
  const ip = require('ip');
3
+ const crypto = require('crypto');
3
4
 
4
5
  'use strict';
5
6
  var utils = module.exports = {
@@ -361,5 +362,9 @@ var utils = module.exports = {
361
362
  }
362
363
  }
363
364
  return target;
365
+ },
366
+
367
+ generateSecretCode: function(byteLength = 32) {
368
+ return crypto.randomBytes(byteLength).toString('hex');
364
369
  }
365
370
  }
@@ -24,6 +24,7 @@ module.exports = {
24
24
  // - 'common': Less detailed than 'combined', omitting the referrer and user-agent.
25
25
  // - 'short': Shorter format that includes the remote address and request details.
26
26
  // - 'tiny': Minimalist format, showing just the method, URL, status, response length, and response time.
27
+ // - 'none': Completely disables HTTP request logging to clean the console for custom debugging scripts and reduce I/O overhead.
27
28
  //
28
29
  // Default Value:
29
30
  // - 'combined': By default, logApiLevel is set to 'combined', providing detailed logs suitable for thorough tracking and analysis.
@@ -91,8 +92,10 @@ module.exports = {
91
92
 
92
93
  // Used to enable security, authentication and authorization and crypt Token
93
94
  //secureEnabled: true,
94
- //secretCode: 'frangoteam751',
95
- //tokenExpiresIn: '1h' // '1h'=1hour, 60=60seconds, '1d'=1day
95
+ //secretCode: '<set-a-strong-random-secret>',
96
+ //tokenExpiresIn: '1h', // '1h'=1hour, 60=60seconds, '1d'=1day
97
+ //enableRefreshCookieAuth: false, // if true, use refresh token HttpOnly cookie flow
98
+ //refreshTokenExpiresIn: '7d' // '7d'=7days, 12h=12hours, 3600=3600seconds
96
99
 
97
100
  // Heartbeat interval in seconds (1-20)
98
101
  heartbeatIntervalSec: 10,