@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.
- package/api/auth/index.js +141 -3
- package/api/index.js +18 -2
- package/api/jwt-helper.js +3 -1
- package/api/resources/index.js +19 -2
- package/dist/3rdpartylicenses.txt +139 -7
- package/dist/assets/i18n/de.json +10 -0
- package/dist/assets/i18n/en.json +14 -3
- package/dist/assets/i18n/es.json +12 -0
- package/dist/assets/i18n/fr.json +10 -0
- package/dist/assets/i18n/ja.json +15 -6
- package/dist/assets/i18n/ko.json +12 -0
- package/dist/assets/i18n/pt.json +9 -2
- package/dist/assets/i18n/ru.json +11 -0
- package/dist/assets/i18n/sv.json +10 -1
- package/dist/assets/i18n/tr.json +8 -1
- package/dist/assets/i18n/ua.json +9 -2
- package/dist/assets/i18n/zh-cn.json +10 -0
- package/dist/assets/i18n/zh-tw.json +11 -1
- package/dist/index.html +2 -2
- package/dist/main.bafae830903d548e.js +329 -0
- package/dist/polyfills.d7de05f9af2fb559.js +1 -0
- package/dist/{runtime.8ef63094e52a66ba.js → runtime.9136a61a9b98f987.js} +1 -1
- package/dist/{scripts.40b60f02658462e4.js → scripts.d9e6ee984bf6f3b7.js} +1 -1
- package/dist/styles.545e37beb3e671ba.css +1 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.js +8 -2
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.js +12 -12
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.js +14 -10
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.js +8 -2
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.js +24 -20
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.js +8 -2
- package/main.js +35 -17
- package/package.json +10 -5
- package/runtime/devices/adsclient/index.js +1 -1
- package/runtime/devices/ethernetip/index.js +1 -1
- package/runtime/devices/gpio/index.js +1 -1
- package/runtime/devices/odbc/index.js +5 -5
- package/runtime/devices/template/index.js +14 -14
- package/runtime/storage/daqstorage.js +28 -2
- package/runtime/storage/questdb/index.js +224 -0
- package/runtime/utils.js +5 -0
- package/settings.default.js +5 -2
- package/dist/main.92522279642ef880.js +0 -329
- package/dist/polyfills.c8e7db9850a3ad8b.js +0 -1
- 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
|
}
|
package/settings.default.js
CHANGED
|
@@ -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: '
|
|
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,
|