node-red-contrib-knx-ultimate 8.1.1 → 8.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/CHANGELOG.md +13 -0
- package/nodes/commonFunctions.js +13 -151
- package/nodes/knxUltimate-config.html +96 -158
- package/nodes/knxUltimate-config.js +41 -51
- package/nodes/knxUltimate.html +4 -0
- package/nodes/knxUltimateUtility.html +7 -0
- package/nodes/knxUltimateViewer.html +3 -0
- package/nodes/knxUltimateViewer.js +3 -1
- package/nodes/locales/de/knxUltimate-config.html +8 -5
- package/nodes/locales/de/knxUltimate-config.json +22 -20
- package/nodes/locales/de/knxUltimate.html +2 -0
- package/nodes/locales/de/knxUltimateUtility.html +6 -0
- package/nodes/locales/de/knxUltimateViewer.html +2 -0
- package/nodes/locales/en/knxUltimate-config.html +8 -5
- package/nodes/locales/en/knxUltimate-config.json +22 -20
- package/nodes/locales/en/knxUltimate.html +2 -0
- package/nodes/locales/en/knxUltimateUtility.html +6 -0
- package/nodes/locales/en/knxUltimateViewer.html +2 -0
- package/nodes/locales/es/knxUltimate-config.html +12 -6
- package/nodes/locales/es/knxUltimate-config.json +22 -20
- package/nodes/locales/es/knxUltimate.html +2 -0
- package/nodes/locales/es/knxUltimateUtility.html +6 -0
- package/nodes/locales/es/knxUltimateViewer.html +2 -0
- package/nodes/locales/fr/knxUltimate-config.html +10 -7
- package/nodes/locales/fr/knxUltimate-config.json +22 -20
- package/nodes/locales/fr/knxUltimate.html +2 -0
- package/nodes/locales/fr/knxUltimateUtility.html +6 -0
- package/nodes/locales/fr/knxUltimateViewer.html +2 -0
- package/nodes/locales/it/knxUltimate-config.html +8 -5
- package/nodes/locales/it/knxUltimate-config.json +22 -20
- package/nodes/locales/it/knxUltimate.html +2 -0
- package/nodes/locales/it/knxUltimateUtility.html +6 -0
- package/nodes/locales/it/knxUltimateViewer.html +2 -0
- package/nodes/locales/zh-CN/knxUltimate-config.html +8 -5
- package/nodes/locales/zh-CN/knxUltimate-config.json +22 -20
- package/nodes/locales/zh-CN/knxUltimate.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateUtility.html +6 -0
- package/nodes/locales/zh-CN/knxUltimateViewer.html +2 -0
- package/nodes/utils/fileStorage.js +376 -0
- package/nodes/utils/knxUtilityProfiles/runtime/autoresponder.js +15 -19
- package/nodes/utils/knxUtilityProfiles/runtime/logger.js +7 -4
- package/nodes/utils/knxUtilityProfiles/runtime/scenecontroller.js +12 -44
- package/nodes/utils/knxViewerHistory.js +49 -77
- package/package.json +2 -2
- package/resources/htmlUtils.js +24 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 8.3.0** - October 2026
|
|
10
|
+
|
|
11
|
+
- Updated the KNX engine to `knxultimate` 6.3.0.
|
|
12
|
+
- New **File Cache Control** tab in the KNX gateway configuration, to reduce the wear of SD cards ([#536](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/536)). The **Disk-wear management** option sets when every file written by KNX Ultimate (GA values, Scene Controller scenes, AutoResponder values, Logger file, Viewer history) reaches the disk: **Freshness priority** (every 5 seconds, default), **Balanced** (when the gateway connects or disconnects), **Custom interval** (from 5 seconds to 30 days) or **Disable writing** (data kept in memory only, lost at every Node-RED restart). The log is not affected.
|
|
13
|
+
- Files are now written only when their content changes: the GA values file is no longer rewritten every 5 seconds when nothing has changed.
|
|
14
|
+
- When writing is disabled, the nodes that use files show a reminder in their editor.
|
|
15
|
+
- New **Wait for the tunnelling ACK (Tunnel UDP)** option in the **Advanced** tab of the KNX gateway configuration. **Yes** waits for the KNX/IP interface to confirm every telegram, sends an unconfirmed telegram once more and then reconnects, also when the bus ACK request is suppressed: select it together with **Suppress the bus ACK request** for eibPORT and Loxone gateways. **Auto** (default) keeps the previous behaviour.
|
|
16
|
+
- **Suppress ACK request in tunneling mode** is now **Suppress the bus ACK request**, with a clearer description in the editor and in the documentation: it also applies to Multicast and Serial FT1.2.
|
|
17
|
+
- The log level moved from the **Advanced** tab to the new **File Cache Control** tab.
|
|
18
|
+
- Removed the **Utility** tab of the gateway configuration.
|
|
19
|
+
- The Viewer no longer rewrites its oldest history file every minute: a whole hour of history is deleted once it has fully expired.
|
|
20
|
+
- Removed 145 duplicated wiki pages with a double language prefix.
|
|
21
|
+
|
|
9
22
|
**Version 8.1.1** - October 2026
|
|
10
23
|
|
|
11
24
|
- Updated the KNX engine to `knxultimate` 6.2.1.
|
package/nodes/commonFunctions.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Utility function
|
|
2
2
|
// until node-red 3.1.0, there is a bug creating a plugin, so for backward compatibility, i must use a JS as a node.
|
|
3
3
|
const oOS = require('os')
|
|
4
|
-
const fs = require('fs')
|
|
5
4
|
const path = require('path')
|
|
6
5
|
const dptlib = require('knxultimate').dptlib
|
|
7
6
|
const KNXClient = require('knxultimate').KNXClient
|
|
8
7
|
const { normalizeAuthFromAccessTokenQuery } = require('./utils/httpAdminAccessToken')
|
|
8
|
+
const { storageOf } = require('./utils/fileStorage')
|
|
9
9
|
|
|
10
10
|
// DATAPONT MANIPULATION HELPERS
|
|
11
11
|
// ####################
|
|
@@ -66,17 +66,6 @@ module.exports = (RED) => {
|
|
|
66
66
|
|
|
67
67
|
// }
|
|
68
68
|
|
|
69
|
-
// 11/03/2020 Delete scene saved file, from html
|
|
70
|
-
RED.httpAdmin.get('/knxultimatescenecontrollerdelete'), (req, res) => {
|
|
71
|
-
// Delete the file
|
|
72
|
-
try {
|
|
73
|
-
const serverId = RED.nodes.getNode(req.query.serverId) // Retrieve node.id of the config node.
|
|
74
|
-
const newPath = `${serverId.userDir}/scenecontroller/SceneController_${req.query.FileName}`
|
|
75
|
-
fs.unlinkSync(newPath)
|
|
76
|
-
} catch (error) { if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn(`e ${error}`) }
|
|
77
|
-
res.json({ status: 220 })
|
|
78
|
-
}
|
|
79
|
-
|
|
80
69
|
// Endpoint for reading csv/esf by the other nodes
|
|
81
70
|
RED.httpAdmin.get('/knxUltimatecsv', RED.auth.needsPermission('knxUltimate-config.read'), (req, res) => {
|
|
82
71
|
try {
|
|
@@ -147,23 +136,6 @@ module.exports = (RED) => {
|
|
|
147
136
|
}
|
|
148
137
|
})
|
|
149
138
|
|
|
150
|
-
// 12/08/2021 Endpoint for deleting the GA persistent file for the current gateway
|
|
151
|
-
RED.httpAdmin.get('/deletePersistGAFile', RED.auth.needsPermission('knxUltimate-config.read'), (req, res) => {
|
|
152
|
-
try {
|
|
153
|
-
if (typeof req.query.serverId !== 'undefined' && req.query.serverId !== null && req.query.serverId !== '') {
|
|
154
|
-
try {
|
|
155
|
-
const serverId = RED.nodes.getNode(req.query.serverId) // Retrieve node.id of the config node.
|
|
156
|
-
const sFile = path.join(serverId.userDir, 'knxpersistvalues', `knxpersist${req.query.serverId}.json`)
|
|
157
|
-
fs.unlinkSync(sFile)
|
|
158
|
-
} catch (error) { res.json({ error: error.stack }) }
|
|
159
|
-
res.json({ error: 'No error' })
|
|
160
|
-
} else {
|
|
161
|
-
res.json({ error: 'No serverId specified' })
|
|
162
|
-
}
|
|
163
|
-
} catch (error) {
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
|
|
167
139
|
// Shared Logger download for legacy nodes and the private Utility profile.
|
|
168
140
|
const downloadLoggerFile = (req, res) => {
|
|
169
141
|
try {
|
|
@@ -182,19 +154,24 @@ module.exports = (RED) => {
|
|
|
182
154
|
res.status(404).json({ error: 'NO_FILE_PATH' })
|
|
183
155
|
return
|
|
184
156
|
}
|
|
185
|
-
|
|
157
|
+
// The file may still be waiting in the gateway's File Cache Control.
|
|
158
|
+
const storage = storageOf(loggerNode.serverKNX)
|
|
159
|
+
if (!storage.exists(filePath)) {
|
|
186
160
|
res.status(404).json({ error: 'FILE_NOT_FOUND' })
|
|
187
161
|
return
|
|
188
162
|
}
|
|
163
|
+
let content
|
|
164
|
+
try {
|
|
165
|
+
content = storage.readFile(filePath)
|
|
166
|
+
} catch (err) {
|
|
167
|
+
try { RED.log.error(`KNXUltimateLoggerDownload error reading file ${filePath}: ${err.message}`) } catch (e) {}
|
|
168
|
+
res.status(500).json({ error: 'READ_ERROR' })
|
|
169
|
+
return
|
|
170
|
+
}
|
|
189
171
|
const safeName = path.basename(filePath) || 'knx-logger.xml'
|
|
190
172
|
res.setHeader('Content-Type', 'application/xml; charset=utf-8')
|
|
191
173
|
res.setHeader('Content-Disposition', `attachment; filename="${safeName}"`)
|
|
192
|
-
|
|
193
|
-
stream.on('error', (err) => {
|
|
194
|
-
try { RED.log.error(`KNXUltimateLoggerDownload error reading file ${filePath}: ${err.message}`) } catch (e) {}
|
|
195
|
-
if (!res.headersSent) res.status(500).json({ error: 'READ_ERROR' })
|
|
196
|
-
})
|
|
197
|
-
stream.pipe(res)
|
|
174
|
+
res.end(content)
|
|
198
175
|
} catch (error) {
|
|
199
176
|
try { RED.log.error(`KNXUltimateLoggerDownload error: ${error.message}`) } catch (e) {}
|
|
200
177
|
if (!res.headersSent) res.status(500).json({ error: 'UNEXPECTED_ERROR' })
|
|
@@ -246,121 +223,6 @@ module.exports = (RED) => {
|
|
|
246
223
|
}
|
|
247
224
|
})
|
|
248
225
|
|
|
249
|
-
// 2026-06 Reveal all keyring passwords (and the general keyring password) in clear text.
|
|
250
|
-
// Used by the "Utility" tab button, enabled only when KNX Secure is selected.
|
|
251
|
-
RED.httpAdmin.get('/knxUltimateKeyringDump', RED.auth.needsPermission('knxUltimate-config.read'), async (req, res) => {
|
|
252
|
-
try {
|
|
253
|
-
let keyringContent = (req.query.keyring || '').toString()
|
|
254
|
-
let password = (req.query.pwd || '').toString()
|
|
255
|
-
// '__PWRD__' is the Node-RED placeholder for an unchanged password credential: ignore it.
|
|
256
|
-
if (password === '__PWRD__') password = ''
|
|
257
|
-
// Fill any missing field (keyring and/or password) from the existing config node.
|
|
258
|
-
if ((!keyringContent || !password) && req.query.serverId) {
|
|
259
|
-
const cfg = RED.nodes.getNode(req.query.serverId)
|
|
260
|
-
if (cfg) {
|
|
261
|
-
if (!keyringContent) { try { keyringContent = cfg.keyringFileXML || keyringContent } catch (e) { } }
|
|
262
|
-
if (!password) { try { password = (cfg.credentials && cfg.credentials.keyringFilePassword) ? cfg.credentials.keyringFilePassword : password } catch (e) { } }
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (!keyringContent || !password) {
|
|
266
|
-
return res.json({ ok: false, error: 'MISSING_KEYRING_OR_PASSWORD' })
|
|
267
|
-
}
|
|
268
|
-
let Keyring
|
|
269
|
-
try {
|
|
270
|
-
({ Keyring } = require('knxultimate/build/secure/keyring'))
|
|
271
|
-
} catch (e) {
|
|
272
|
-
try { RED.log.error(`KNXUltimate: cannot load Keyring module: ${e.message}`) } catch (err) { }
|
|
273
|
-
return res.json({ ok: false, error: 'KEYRING_MODULE_UNAVAILABLE' })
|
|
274
|
-
}
|
|
275
|
-
const kr = new Keyring()
|
|
276
|
-
try {
|
|
277
|
-
await kr.load(keyringContent, password)
|
|
278
|
-
} catch (e) {
|
|
279
|
-
try { RED.log.error(`KNXUltimate: keyring load error: ${e.message}`) } catch (err) { }
|
|
280
|
-
return res.json({ ok: false, error: 'KEYRING_LOAD_FAILED' })
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const toIAString = (value) => {
|
|
284
|
-
if (!value) return ''
|
|
285
|
-
return typeof value.toString === 'function' ? value.toString() : String(value)
|
|
286
|
-
}
|
|
287
|
-
const toBufferString = (value) => {
|
|
288
|
-
if (!value) return ''
|
|
289
|
-
if (Buffer.isBuffer(value)) return value.toString('hex')
|
|
290
|
-
return String(value)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const lines = []
|
|
294
|
-
lines.push('================ KNX Secure keyring passwords ================')
|
|
295
|
-
lines.push(`Created By: ${kr.getCreatedBy?.() || ''}`)
|
|
296
|
-
lines.push(`Created On: ${kr.getCreated?.() || ''}`)
|
|
297
|
-
lines.push(`General keyring password: ${password}`)
|
|
298
|
-
lines.push('')
|
|
299
|
-
|
|
300
|
-
lines.push('Interfaces:')
|
|
301
|
-
const interfaceMap = kr.getInterfaces?.()
|
|
302
|
-
const interfaces = Array.from(interfaceMap ? interfaceMap.values() : [])
|
|
303
|
-
if (interfaces.length === 0) {
|
|
304
|
-
lines.push(' (none)')
|
|
305
|
-
} else {
|
|
306
|
-
interfaces.forEach((iface, idx) => {
|
|
307
|
-
lines.push(` [${idx + 1}] ${toIAString(iface.individualAddress) || '(unknown)'} (${iface.type || ''})`)
|
|
308
|
-
lines.push(` Host: ${toIAString(iface.host) || ''}`)
|
|
309
|
-
lines.push(` User ID: ${typeof iface.userId === 'number' ? iface.userId : ''}`)
|
|
310
|
-
lines.push(` Password: ${iface.decryptedPassword || ''}`)
|
|
311
|
-
lines.push(` Authentication: ${iface.decryptedAuthentication || ''}`)
|
|
312
|
-
})
|
|
313
|
-
}
|
|
314
|
-
lines.push('')
|
|
315
|
-
|
|
316
|
-
lines.push('Backbones:')
|
|
317
|
-
const backbones = kr.getBackbones?.() || []
|
|
318
|
-
if (backbones.length === 0) {
|
|
319
|
-
lines.push(' (none)')
|
|
320
|
-
} else {
|
|
321
|
-
backbones.forEach((backbone, idx) => {
|
|
322
|
-
lines.push(` [${idx + 1}] Multicast: ${backbone.multicastAddress || ''}`)
|
|
323
|
-
lines.push(` Key (hex): ${toBufferString(backbone.decryptedKey)}`)
|
|
324
|
-
})
|
|
325
|
-
}
|
|
326
|
-
lines.push('')
|
|
327
|
-
|
|
328
|
-
lines.push('Group Addresses:')
|
|
329
|
-
const groupAddressMap = kr.getGroupAddresses?.()
|
|
330
|
-
const groupAddresses = Array.from(groupAddressMap ? groupAddressMap.values() : [])
|
|
331
|
-
if (groupAddresses.length === 0) {
|
|
332
|
-
lines.push(' (none)')
|
|
333
|
-
} else {
|
|
334
|
-
groupAddresses.forEach((group, idx) => {
|
|
335
|
-
lines.push(` [${idx + 1}] ${toIAString(group.address) || ''}`)
|
|
336
|
-
lines.push(` Key (hex): ${toBufferString(group.decryptedKey)}`)
|
|
337
|
-
})
|
|
338
|
-
}
|
|
339
|
-
lines.push('')
|
|
340
|
-
|
|
341
|
-
lines.push('Devices:')
|
|
342
|
-
const deviceMap = kr.getDevices?.()
|
|
343
|
-
const devices = Array.from(deviceMap ? deviceMap.values() : [])
|
|
344
|
-
if (devices.length === 0) {
|
|
345
|
-
lines.push(' (none)')
|
|
346
|
-
} else {
|
|
347
|
-
devices.forEach((device, idx) => {
|
|
348
|
-
lines.push(` [${idx + 1}] ${toIAString(device.individualAddress) || ''}`)
|
|
349
|
-
lines.push(` Tool Key (hex): ${toBufferString(device.decryptedToolKey)}`)
|
|
350
|
-
lines.push(` Management Password: ${device.decryptedManagementPassword || ''}`)
|
|
351
|
-
lines.push(` Authentication: ${device.decryptedAuthentication || ''}`)
|
|
352
|
-
lines.push(` Serial Number: ${device.serialNumber || ''}`)
|
|
353
|
-
})
|
|
354
|
-
}
|
|
355
|
-
lines.push('================ End of keyring passwords ================')
|
|
356
|
-
|
|
357
|
-
res.json({ ok: true, dump: lines.join('\n') })
|
|
358
|
-
} catch (error) {
|
|
359
|
-
try { RED.log.error(`KNXUltimate: knxUltimateKeyringDump error: ${error.message}`) } catch (e) { }
|
|
360
|
-
res.json({ ok: false, error: 'UNEXPECTED_ERROR' })
|
|
361
|
-
}
|
|
362
|
-
})
|
|
363
|
-
|
|
364
226
|
RED.httpAdmin.get('/knxUltimateKeyringDataSecureGAs', RED.auth.needsPermission('knxUltimate-config.read'), async (req, res) => {
|
|
365
227
|
try {
|
|
366
228
|
let keyringContent = (req.query.keyring || '').toString()
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
const optionalNumberValidator = function (value) {
|
|
5
5
|
return (value === '' || value === null || typeof value === 'undefined') ? true : RED.validators.number()(value)
|
|
6
6
|
}
|
|
7
|
+
// File Cache Control: the custom interval must be between 5 seconds and 30 days.
|
|
8
|
+
const fileCacheIntervalValidator = function (value) {
|
|
9
|
+
const field = (name, fallback) => {
|
|
10
|
+
const element = $("#node-config-input-" + name);
|
|
11
|
+
return element.length ? element.val() : fallback;
|
|
12
|
+
};
|
|
13
|
+
if (field('fileCacheMode', this.fileCacheMode) !== 'custom') return true;
|
|
14
|
+
const seconds = Number(value) * ({ s: 1, m: 60, h: 3600, d: 86400 }[field('fileCacheIntervalUnit', this.fileCacheIntervalUnit)] || 1);
|
|
15
|
+
return Number.isFinite(seconds) && seconds >= 5 && seconds <= 30 * 86400;
|
|
16
|
+
}
|
|
7
17
|
|
|
8
18
|
RED.nodes.registerType('knxUltimate-config', {
|
|
9
19
|
category: 'config',
|
|
@@ -15,11 +25,15 @@
|
|
|
15
25
|
hostProtocol: { value: "Auto", required: false }, // TunnelUDP/TunnelTCP/Multicast
|
|
16
26
|
// enable this option to suppress the acknowledge flag with outgoing L_Data.req requests. LoxOne needs this
|
|
17
27
|
suppressACKRequest: { value: false },
|
|
28
|
+
waitForTunnelingAck: { value: "auto" }, // TunnelUDP: auto follows suppressACKRequest, as the KNX engine does
|
|
18
29
|
csv: { value: "", required: false },
|
|
19
30
|
KNXEthInterface: { value: "Auto" },
|
|
20
31
|
KNXEthInterfaceManuallyInput: { value: "" },
|
|
21
32
|
stopETSImportIfNoDatapoint: { value: "fake" },
|
|
22
33
|
loglevel: { value: "error" },
|
|
34
|
+
fileCacheMode: { value: "freshness" },
|
|
35
|
+
fileCacheInterval: { value: 10, validate: fileCacheIntervalValidator },
|
|
36
|
+
fileCacheIntervalUnit: { value: "m" },
|
|
23
37
|
name: { value: "KNX Gateway" },
|
|
24
38
|
delaybetweentelegrams: { value: 25, required: false, validate: RED.validators.number() },
|
|
25
39
|
ignoreTelegramsWithRepeatedFlag: { value: false, required: false },
|
|
@@ -164,13 +178,13 @@
|
|
|
164
178
|
active: (node.knxSecureSelected === true || node.knxSecureSelected === 'true') ? 1 : 0,
|
|
165
179
|
activate: function (event, ui) {
|
|
166
180
|
node.knxSecureSelected = $(ui.newTab).index() === 1;
|
|
167
|
-
try { updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP();
|
|
181
|
+
try { updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); } catch (e) { }
|
|
168
182
|
}
|
|
169
183
|
});
|
|
170
184
|
// Ensure the correct tab is enforced after all UI init
|
|
171
185
|
try {
|
|
172
186
|
const initialActive = (node.knxSecureSelected === true || node.knxSecureSelected === 'true') ? 1 : 0;
|
|
173
|
-
setTimeout(() => { $("#tabsMain").tabs("option", "active", initialActive); updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP();
|
|
187
|
+
setTimeout(() => { $("#tabsMain").tabs("option", "active", initialActive); updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); }, 0);
|
|
174
188
|
} catch (e) { }
|
|
175
189
|
|
|
176
190
|
// Keyring ACE editor setup
|
|
@@ -973,123 +987,32 @@
|
|
|
973
987
|
try { updateSecureModeAvailability(); } catch (e) { }
|
|
974
988
|
|
|
975
989
|
|
|
976
|
-
// NOTE: Do not auto-delete persisted GA cache on editor open.
|
|
977
|
-
// Users can explicitly clear it from the Utility tab.
|
|
978
|
-
|
|
979
990
|
// 06/07/2023 Tabs
|
|
980
991
|
// *****************************
|
|
981
992
|
$("#tabs").tabs();
|
|
982
993
|
// *****************************
|
|
983
994
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
$("#
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
$("#clearpersistga").click(function () {
|
|
1008
|
-
try {
|
|
1009
|
-
const confirmText = node._("knxUltimate-config.utility.clear_persist_confirm");
|
|
1010
|
-
const okText = node._("knxUltimate-config.utility.clear_persist_ok");
|
|
1011
|
-
const cancelText = node._("knxUltimate-config.utility.clear_persist_cancel");
|
|
1012
|
-
const doneText = node._("knxUltimate-config.utility.clear_persist_done");
|
|
1013
|
-
const failText = node._("knxUltimate-config.utility.clear_persist_fail");
|
|
1014
|
-
|
|
1015
|
-
var dlg = RED.notify(confirmText, {
|
|
1016
|
-
modal: true,
|
|
1017
|
-
fixed: true,
|
|
1018
|
-
type: "warning",
|
|
1019
|
-
buttons: [
|
|
1020
|
-
{
|
|
1021
|
-
text: cancelText,
|
|
1022
|
-
click: function () { dlg.close(); }
|
|
1023
|
-
},
|
|
1024
|
-
{
|
|
1025
|
-
text: okText,
|
|
1026
|
-
class: "primary",
|
|
1027
|
-
click: function () {
|
|
1028
|
-
dlg.close();
|
|
1029
|
-
$.getJSON("deletePersistGAFile?serverId=" + node.id, (data) => {
|
|
1030
|
-
RED.notify(doneText, { type: "success", timeout: 2500 });
|
|
1031
|
-
}).fail(function (jqXHR, textStatus, errorThrown) {
|
|
1032
|
-
const err = (errorThrown || textStatus || "").toString();
|
|
1033
|
-
RED.notify(failText + (err ? (": " + err) : ""), { type: "error", timeout: 5000 });
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
]
|
|
1038
|
-
});
|
|
1039
|
-
} catch (e) { }
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
|
-
// Utility: reveal all keyring passwords in clear text.
|
|
1043
|
-
// Enabled only when KNX Secure is selected.
|
|
1044
|
-
function updateRevealKeyringButton() {
|
|
1045
|
-
try {
|
|
1046
|
-
const isSecure = isSecureTabActive();
|
|
1047
|
-
$("#revealkeyringpwd").prop('disabled', !isSecure)
|
|
1048
|
-
.css('opacity', isSecure ? 1 : 0.5)
|
|
1049
|
-
.css('cursor', isSecure ? 'pointer' : 'not-allowed');
|
|
1050
|
-
} catch (e) { }
|
|
1051
|
-
}
|
|
1052
|
-
updateRevealKeyringButton();
|
|
1053
|
-
|
|
1054
|
-
$("#revealkeyringpwd").click(function () {
|
|
1055
|
-
if (!isSecureTabActive()) return;
|
|
1056
|
-
try {
|
|
1057
|
-
const keyring = (node._keyringEditor && typeof node._keyringEditor.getValue === 'function')
|
|
1058
|
-
? node._keyringEditor.getValue()
|
|
1059
|
-
: $("#node-config-input-keyringFileXML").val();
|
|
1060
|
-
const pwd = $("#node-config-input-keyringFilePassword").val();
|
|
1061
|
-
const params = { serverId: node.id };
|
|
1062
|
-
if (typeof keyring === 'string' && keyring.trim() !== '') params.keyring = keyring.trim();
|
|
1063
|
-
// '__PWRD__' is the Node-RED placeholder for an unchanged password credential:
|
|
1064
|
-
// never send it, so the backend falls back to the real stored credential.
|
|
1065
|
-
if (typeof pwd === 'string' && pwd.trim() !== '' && pwd !== '__PWRD__') params.pwd = pwd.trim();
|
|
1066
|
-
$("#divDebugText").show();
|
|
1067
|
-
$("#debugText").val(node._('knxUltimate-config.utility.reveal_keyring_loading'));
|
|
1068
|
-
const scrollToDebugText = function () {
|
|
1069
|
-
try {
|
|
1070
|
-
const el = document.getElementById('debugText');
|
|
1071
|
-
if (el && typeof el.scrollIntoView === 'function') {
|
|
1072
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1073
|
-
}
|
|
1074
|
-
} catch (e) { }
|
|
1075
|
-
};
|
|
1076
|
-
scrollToDebugText();
|
|
1077
|
-
$.getJSON('knxUltimateKeyringDump', params, (data) => {
|
|
1078
|
-
if (data && data.ok) {
|
|
1079
|
-
$("#debugText").val(data.dump);
|
|
1080
|
-
} else {
|
|
1081
|
-
const code = (data && data.error) ? data.error : 'UNEXPECTED_ERROR';
|
|
1082
|
-
$("#debugText").val(node._('knxUltimate-config.utility.reveal_keyring_fail') + ' (' + code + ')');
|
|
1083
|
-
}
|
|
1084
|
-
scrollToDebugText();
|
|
1085
|
-
}).fail(function (jqXHR, textStatus, errorThrown) {
|
|
1086
|
-
const err = (errorThrown || textStatus || '').toString();
|
|
1087
|
-
$("#debugText").val(node._('knxUltimate-config.utility.reveal_keyring_fail') + (err ? (': ' + err) : ''));
|
|
1088
|
-
});
|
|
1089
|
-
} catch (e) { }
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
|
|
995
|
+
// Gateways saved by older versions lack these options: Node-RED then leaves
|
|
996
|
+
// their fields empty, so show the defaults the runtime uses.
|
|
997
|
+
const applyMissingDefault = (field, value) => {
|
|
998
|
+
const $field = $("#node-config-input-" + field);
|
|
999
|
+
if ($field.val() === null || $field.val() === '') $field.val(value);
|
|
1000
|
+
};
|
|
1001
|
+
applyMissingDefault('fileCacheMode', 'freshness');
|
|
1002
|
+
applyMissingDefault('fileCacheInterval', 10);
|
|
1003
|
+
applyMissingDefault('fileCacheIntervalUnit', 'm');
|
|
1004
|
+
applyMissingDefault('waitForTunnelingAck', 'auto');
|
|
1005
|
+
|
|
1006
|
+
// File Cache Control: show the custom interval and the "writing disabled" tip.
|
|
1007
|
+
const syncFileCacheRows = () => {
|
|
1008
|
+
const mode = $("#node-config-input-fileCacheMode").val();
|
|
1009
|
+
$("#knxUltimate-fileCacheInterval-row").toggle(mode === 'custom');
|
|
1010
|
+
$("#knxUltimate-fileCacheDisabled-tip").toggle(mode === 'disabled');
|
|
1011
|
+
$("#node-config-input-fileCacheInterval").trigger('change');
|
|
1012
|
+
};
|
|
1013
|
+
$("#knxUltimate-fileCacheDisabled-tip").text(node._("knxUltimate-config.file_cache.disabled_tip"));
|
|
1014
|
+
$("#node-config-input-fileCacheMode, #node-config-input-fileCacheIntervalUnit").on('change', syncFileCacheRows);
|
|
1015
|
+
syncFileCacheRows();
|
|
1093
1016
|
|
|
1094
1017
|
},
|
|
1095
1018
|
oneditcancel: function () {
|
|
@@ -1362,7 +1285,7 @@
|
|
|
1362
1285
|
<li><a href="#tabs-1"><i class="fa fa-list-ol"></i> <span data-i18n="knxUltimate-config.tabs.configuration"></span></a></li>
|
|
1363
1286
|
<li><a href="#tabs-2"><i class="fa fa-braille"></i> <span data-i18n="knxUltimate-config.tabs.advanced"></span></a></li>
|
|
1364
1287
|
<li><a href="#tabs-3"><i class="fa fa-code"></i> <span data-i18n="knxUltimate-config.tabs.ets_import"></span></a></li>
|
|
1365
|
-
<li><a href="#tabs-4"><i class="fa fa-
|
|
1288
|
+
<li><a href="#tabs-4"><i class="fa fa-hdd-o"></i> <span data-i18n="knxUltimate-config.tabs.file_cache"></span></a></li>
|
|
1366
1289
|
</ul>
|
|
1367
1290
|
<div id="tabs-1">
|
|
1368
1291
|
<p>
|
|
@@ -1480,6 +1403,20 @@
|
|
|
1480
1403
|
<span data-i18n="knxUltimate-config.advanced.suppress_ack"></span>
|
|
1481
1404
|
</label>
|
|
1482
1405
|
</div>
|
|
1406
|
+
<div class="form-tips" data-i18n="knxUltimate-config.advanced.suppress_ack_help"></div>
|
|
1407
|
+
|
|
1408
|
+
<div class="form-row">
|
|
1409
|
+
<label for="node-config-input-waitForTunnelingAck" style="width:auto;">
|
|
1410
|
+
<i class="fa fa-exchange"></i>
|
|
1411
|
+
<span data-i18n="knxUltimate-config.advanced.wait_tunneling_ack"></span>
|
|
1412
|
+
</label>
|
|
1413
|
+
<select id="node-config-input-waitForTunnelingAck" style="width:60%;">
|
|
1414
|
+
<option value="auto" data-i18n="knxUltimate-config.advanced.wait_tunneling_ack_auto"></option>
|
|
1415
|
+
<option value="yes" data-i18n="knxUltimate-config.advanced.wait_tunneling_ack_yes"></option>
|
|
1416
|
+
<option value="no" data-i18n="knxUltimate-config.advanced.wait_tunneling_ack_no"></option>
|
|
1417
|
+
</select>
|
|
1418
|
+
</div>
|
|
1419
|
+
<div class="form-tips" data-i18n="knxUltimate-config.advanced.wait_tunneling_ack_help"></div>
|
|
1483
1420
|
|
|
1484
1421
|
<div class="form-row form-row-checkbox">
|
|
1485
1422
|
<input type="checkbox" id="node-config-input-enableFlowBubbles"
|
|
@@ -1500,22 +1437,6 @@
|
|
|
1500
1437
|
<input type="number" id="node-config-input-delaybetweentelegrams" style="width:20%">
|
|
1501
1438
|
</div>
|
|
1502
1439
|
|
|
1503
|
-
<div class="form-row">
|
|
1504
|
-
<label for="node-config-input-loglevel">
|
|
1505
|
-
<i class="fa fa-question-circle"></i>
|
|
1506
|
-
<span data-i18n="knxUltimate-config.advanced.log_level"></span>
|
|
1507
|
-
</label>
|
|
1508
|
-
<select id="node-config-input-loglevel" style="width:40%;">
|
|
1509
|
-
<option value="disable" data-i18n="knxUltimate-config.advanced.select_silent"></option>
|
|
1510
|
-
<option value="error" data-i18n="knxUltimate-config.advanced.select_error"></option>
|
|
1511
|
-
<option value="warn" data-i18n="knxUltimate-config.advanced.select_warning"></option>
|
|
1512
|
-
<option value="info" data-i18n="knxUltimate-config.advanced.select_info"></option>
|
|
1513
|
-
<option value="debug" data-i18n="knxUltimate-config.advanced.select_debug"></option>
|
|
1514
|
-
</select>
|
|
1515
|
-
<!-- <// DEBUG LEVELS: success < debug < info < warn < error < disable
|
|
1516
|
-
</div> -->
|
|
1517
|
-
</div>
|
|
1518
|
-
|
|
1519
1440
|
<div class="form-row">
|
|
1520
1441
|
<label> <span style="font-weight:bold">Node Status</span>
|
|
1521
1442
|
</label>
|
|
@@ -1602,36 +1523,53 @@
|
|
|
1602
1523
|
</p>
|
|
1603
1524
|
</div>
|
|
1604
1525
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1526
|
+
<div id="tabs-4">
|
|
1527
|
+
<p>
|
|
1528
|
+
<div class="form-row">
|
|
1529
|
+
<label for="node-config-input-loglevel">
|
|
1530
|
+
<i class="fa fa-question-circle"></i>
|
|
1531
|
+
<span data-i18n="knxUltimate-config.advanced.log_level"></span>
|
|
1532
|
+
</label>
|
|
1533
|
+
<select id="node-config-input-loglevel" style="width:40%;">
|
|
1534
|
+
<option value="disable" data-i18n="knxUltimate-config.advanced.select_silent"></option>
|
|
1535
|
+
<option value="error" data-i18n="knxUltimate-config.advanced.select_error"></option>
|
|
1536
|
+
<option value="warn" data-i18n="knxUltimate-config.advanced.select_warning"></option>
|
|
1537
|
+
<option value="info" data-i18n="knxUltimate-config.advanced.select_info"></option>
|
|
1538
|
+
<option value="debug" data-i18n="knxUltimate-config.advanced.select_debug"></option>
|
|
1539
|
+
</select>
|
|
1540
|
+
</div>
|
|
1617
1541
|
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
<
|
|
1621
|
-
|
|
1622
|
-
</
|
|
1542
|
+
<div class="form-row">
|
|
1543
|
+
<label for="node-config-input-fileCacheMode">
|
|
1544
|
+
<i class="fa fa-hdd-o"></i>
|
|
1545
|
+
<span data-i18n="knxUltimate-config.file_cache.mode"></span>
|
|
1546
|
+
</label>
|
|
1547
|
+
<select id="node-config-input-fileCacheMode" style="width:60%;">
|
|
1548
|
+
<option value="freshness" data-i18n="knxUltimate-config.file_cache.mode_freshness"></option>
|
|
1549
|
+
<option value="balanced" data-i18n="knxUltimate-config.file_cache.mode_balanced"></option>
|
|
1550
|
+
<option value="disabled" data-i18n="knxUltimate-config.file_cache.mode_disabled"></option>
|
|
1551
|
+
<option value="custom" data-i18n="knxUltimate-config.file_cache.mode_custom"></option>
|
|
1552
|
+
</select>
|
|
1553
|
+
</div>
|
|
1554
|
+
<div class="form-tips knx-file-cache-disabled-tip" id="knxUltimate-fileCacheDisabled-tip" style="display:none;"></div>
|
|
1623
1555
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
<
|
|
1627
|
-
|
|
1628
|
-
</
|
|
1556
|
+
<div class="form-row" id="knxUltimate-fileCacheInterval-row" style="display:none;">
|
|
1557
|
+
<label for="node-config-input-fileCacheInterval">
|
|
1558
|
+
<i class="fa fa-clock-o"></i>
|
|
1559
|
+
<span data-i18n="knxUltimate-config.file_cache.interval"></span>
|
|
1560
|
+
</label>
|
|
1561
|
+
<input type="number" id="node-config-input-fileCacheInterval" min="1" style="width:100px;">
|
|
1562
|
+
<select id="node-config-input-fileCacheIntervalUnit" style="width:120px;">
|
|
1563
|
+
<option value="s" data-i18n="knxUltimate-config.file_cache.unit_seconds"></option>
|
|
1564
|
+
<option value="m" data-i18n="knxUltimate-config.file_cache.unit_minutes"></option>
|
|
1565
|
+
<option value="h" data-i18n="knxUltimate-config.file_cache.unit_hours"></option>
|
|
1566
|
+
<option value="d" data-i18n="knxUltimate-config.file_cache.unit_days"></option>
|
|
1567
|
+
</select>
|
|
1568
|
+
</div>
|
|
1629
1569
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
</p>
|
|
1634
|
-
</div>
|
|
1570
|
+
<div class="form-tips" data-i18n="[html]knxUltimate-config.file_cache.description"></div>
|
|
1571
|
+
</p>
|
|
1572
|
+
</div>
|
|
1635
1573
|
</div>
|
|
1636
1574
|
|
|
1637
1575
|
</div>
|