node-red-contrib-remote 1.6.1 → 2.0.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.
- package/README.md +16 -0
- package/nodes/locales/de/remote-config.json +2 -2
- package/nodes/locales/en-US/remote-config.json +2 -2
- package/nodes/remote-access.js +152 -83
- package/nodes/remote-commons.js +22 -3
- package/nodes/remote-config.html +43 -20
- package/nodes/remote-config.js +21 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,6 +36,22 @@ You will find more information on [www.remote-red-com](https://www.remote-red.co
|
|
|
36
36
|
|
|
37
37
|
## Version History
|
|
38
38
|
|
|
39
|
+
Version 2.0.1
|
|
40
|
+
- Possible bug on network connection fixed
|
|
41
|
+
|
|
42
|
+
Version 2.0.0
|
|
43
|
+
- Enormously faster access to your Node-RED via the Internet
|
|
44
|
+
- macOS app for Apple Silicon (M-Chips)#
|
|
45
|
+
- Status display whether the instances are connected
|
|
46
|
+
- Changes to the configuration are applied without reconnecting the app
|
|
47
|
+
- Connection loss after switching from Wifi to mobile fixed
|
|
48
|
+
- Notifications with full UTF-8 support
|
|
49
|
+
- New instances automatically recognize http/https and the use of Dashboard 2
|
|
50
|
+
- Android: Local network is used again if possible
|
|
51
|
+
- Android: Delete an instance by long press and pull to refresh
|
|
52
|
+
- Reconnecting the instance to my server improved
|
|
53
|
+
- Multiple use of a config node in several access nodes is now recognized
|
|
54
|
+
|
|
39
55
|
Version 1.6.1
|
|
40
56
|
- Adding instances on Android is be possible again.
|
|
41
57
|
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"region_sg": "Asien (Singapur)",
|
|
16
16
|
"region_dev": "Entwicklung",
|
|
17
17
|
"region_desc": "Der Standort kann nicht mehr geändert werden, sobald eine App registriert wurde.",
|
|
18
|
-
"change_info": "
|
|
19
|
-
"backup_info": "Diese Konfig-Node wurde aus einem Backup wiederhergestellt. Daher sind sicherheitsrelevante Informationen nicht enthalten. Bitte löschen diese Konfig-Node und die Verbindungen in Deinen Remote-RED Apps. Lege eine neue Konfig-Node an, verbinde diese neu mit den Apps und wähle diese neue Konfig-Node in allen davon abhängigen Nodes aus."
|
|
18
|
+
"change_info": "Damit Änderungen aktiv werden führe einen Redeploy durch. Starte danach die App neu oder aktualisiere die Liste der Instanzen, indem Du die Liste nach unten ziehst.",
|
|
19
|
+
"backup_info": "Diese Konfig-Node wurde aus einem Backup wiederhergestellt. Daher sind sicherheitsrelevante Informationen nicht enthalten. Bitte löschen diese Konfig-Node (über das Zahnrad Icon rechts oben) und die Verbindungen in Deinen Remote-RED Apps. Lege eine neue Konfig-Node an, verbinde diese neu mit den Apps und wähle diese neue Konfig-Node in allen davon abhängigen Nodes aus."
|
|
20
20
|
},
|
|
21
21
|
"actions" : {
|
|
22
22
|
"register_app": "Remote-RED App verbinden",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"region_sg": "Asia (Singapur)",
|
|
16
16
|
"region_dev": "Development host",
|
|
17
17
|
"region_desc": "The region can´t be changed after registering an app.",
|
|
18
|
-
"change_info": "
|
|
19
|
-
"backup_info": "This config node was restored from a backup. Therefore security relevant information are not included. Please delete this config-node and the connections in your Remote-RED apps. Create a new config-node, connect it to the apps and select this new config node in all dependent nodes."
|
|
18
|
+
"change_info": "To apply changes, first carry out a redeploy. Then restart the app or refresh the list of instances by dragging the list down.",
|
|
19
|
+
"backup_info": "This config node was restored from a backup. Therefore security relevant information are not included. Please delete this config-node (via the gear icon at the top right) and the connections in your Remote-RED apps. Create a new config-node, connect it to the apps and select this new config node in all dependent nodes."
|
|
20
20
|
},
|
|
21
21
|
"actions" : {
|
|
22
22
|
"register_app": "Connect Remote-RED App",
|
package/nodes/remote-access.js
CHANGED
|
@@ -2,14 +2,24 @@ module.exports = function(RED) {
|
|
|
2
2
|
const commons = require('./remote-commons');
|
|
3
3
|
const child_process = require('child_process');
|
|
4
4
|
const os = require("os");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
let instanceIsBanned = false
|
|
5
|
+
const internalIp = require('internal-ip');
|
|
6
|
+
const instancehashToAccessNode = {};
|
|
8
7
|
|
|
9
8
|
function startSSH(node, server, port) {
|
|
10
9
|
try {
|
|
11
10
|
node.log("starting ssh process");
|
|
12
11
|
|
|
12
|
+
// Is there a old node.sshprocess object?
|
|
13
|
+
if (node.sshprocess !== undefined) {
|
|
14
|
+
node.log(`ssh process with pid ${node.sshprocess.pid} already existing`);
|
|
15
|
+
node.sshprocess.removeAllListeners('close');
|
|
16
|
+
node.sshprocess.removeAllListeners('exit');
|
|
17
|
+
node.sshprocess.removeAllListeners('error');
|
|
18
|
+
node.sshprocess.stdout.removeAllListeners('data');
|
|
19
|
+
node.sshprocess.stderr.removeAllListeners('data');
|
|
20
|
+
node.sshprocess = undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
// Reset heartbeat status
|
|
14
24
|
node.initialHeartbeatStatus = ''
|
|
15
25
|
node.lastHeartbeatStatus = ''
|
|
@@ -24,48 +34,43 @@ module.exports = function(RED) {
|
|
|
24
34
|
if ( node.verbose ) {
|
|
25
35
|
sshparameters.push('-v');
|
|
26
36
|
}
|
|
27
|
-
sshprocess = child_process.spawn("ssh", sshparameters);
|
|
37
|
+
node.sshprocess = child_process.spawn("ssh", sshparameters);
|
|
38
|
+
node.log(`ssh process with pid ${node.sshprocess.pid} started`);
|
|
28
39
|
|
|
29
40
|
// Set serving.. if not working, the process will exit or close
|
|
30
41
|
setStatus(node, {fill:"green",shape:"dot",text:"remote-access.status.serving"});
|
|
31
42
|
node.serving = true
|
|
32
43
|
|
|
33
44
|
// Attach to process events
|
|
34
|
-
sshprocess.stdout.on('data', (data) => {
|
|
45
|
+
node.sshprocess.stdout.on('data', (data) => {
|
|
35
46
|
logSSHBuffer(node, data, 'ssh process stdout');
|
|
36
47
|
});
|
|
37
48
|
|
|
38
|
-
sshprocess.stderr.on('data', (data) => {
|
|
49
|
+
node.sshprocess.stderr.on('data', (data) => {
|
|
39
50
|
logSSHBuffer(node, data, 'ssh process stderr');
|
|
40
51
|
if ( data.toString().includes('Connection timed out')) {
|
|
41
52
|
node.error(`SSH connection timeout. Please check if port 22 is open for outgoing connections.`);
|
|
42
53
|
}
|
|
43
54
|
});
|
|
44
55
|
|
|
45
|
-
sshprocess.on('close', (code, signal) => {
|
|
46
|
-
if ( statustext !== "remote-access.status.heartbeaterror" ) {
|
|
56
|
+
node.sshprocess.on('close', (code, signal) => {
|
|
57
|
+
if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
|
|
47
58
|
setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopped"});
|
|
48
59
|
}
|
|
49
60
|
node.serving = false
|
|
50
|
-
node.log(
|
|
61
|
+
node.log(`ssh process close (pid: ${node.sshprocess.pid} code: ${code} signal: ${signal})`);
|
|
51
62
|
});
|
|
52
63
|
|
|
53
|
-
sshprocess.on('exit', (code, signal) => {
|
|
54
|
-
if ( statustext !== "remote-access.status.heartbeaterror" ) {
|
|
64
|
+
node.sshprocess.on('exit', (code, signal) => {
|
|
65
|
+
if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
|
|
55
66
|
setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopped"});
|
|
56
67
|
}
|
|
57
68
|
node.serving = false
|
|
58
|
-
node.log(
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
sshprocess.on('error', (err) => {
|
|
62
|
-
node.log("ssh process error:" + err.name + ": " + err.message);
|
|
69
|
+
node.log(`ssh process exit (pid: ${node.sshprocess.pid} code: ${code} signal: ${signal})`);
|
|
63
70
|
});
|
|
64
71
|
|
|
65
|
-
node.on('
|
|
66
|
-
|
|
67
|
-
node.log("stopping ssh process");
|
|
68
|
-
sshprocess.kill();
|
|
72
|
+
node.sshprocess.on('error', (err) => {
|
|
73
|
+
node.log(`ssh process error (pid: ${node.sshprocess.pid}): ${err.name} : ${err.message}`);
|
|
69
74
|
});
|
|
70
75
|
} catch (e) {
|
|
71
76
|
// TODO: Error: socket hang up
|
|
@@ -87,57 +92,78 @@ module.exports = function(RED) {
|
|
|
87
92
|
function setStatus(node, options) {
|
|
88
93
|
// Set the status, remember text
|
|
89
94
|
if ( options !== undefined && options.text !== undefined ) {
|
|
90
|
-
statustext = options.text
|
|
95
|
+
node.statustext = options.text
|
|
91
96
|
}
|
|
92
97
|
node.status(options);
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
function requestInstanceSlot(node) {
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
// Asnyc because https call can take some seconds..
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
// Is this instance banned?
|
|
104
|
+
if ( node.instanceIsBanned ) {
|
|
105
|
+
reject();
|
|
106
|
+
}
|
|
100
107
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const port = response.data.port;
|
|
113
|
-
node.log(`Using ${node.confignode.server} on port ${port}`);
|
|
114
|
-
startSSH(node, node.confignode.server, port);
|
|
115
|
-
})
|
|
116
|
-
.catch((error) => {
|
|
117
|
-
// Log error
|
|
118
|
-
node.error('requestInstanceSlot: ' + commons.getNetworkErrorString(error))
|
|
119
|
-
if ( commons.getNetworkErrorCustomString(error) !== undefined) {
|
|
120
|
-
node.error(commons.getNetworkErrorCustomString(error));
|
|
108
|
+
// Create object with config values to send to server
|
|
109
|
+
var localip = node.confignode.host;
|
|
110
|
+
if (localip.toLowerCase() == 'localhost') localip = internalIp.v4.sync();
|
|
111
|
+
if (localip === undefined) localip = "";
|
|
112
|
+
const config = {
|
|
113
|
+
'name': node.confignode.name,
|
|
114
|
+
'localip': localip,
|
|
115
|
+
'localport': node.confignode.port,
|
|
116
|
+
'localprotocol': node.confignode.protocol,
|
|
117
|
+
'baseurl': node.confignode.baseurl,
|
|
118
|
+
'timestamp': Date.now(),
|
|
121
119
|
}
|
|
120
|
+
const configString = JSON.stringify(config);
|
|
121
|
+
const configStringBuffer = Buffer.from(configString);
|
|
122
|
+
const configStringBase64 = configStringBuffer.toString('base64');
|
|
123
|
+
node.log(`Sending config to server: ${configString}`);
|
|
124
|
+
|
|
125
|
+
// Call API to retrive server and port.
|
|
126
|
+
const axiosInstance = commons.createAxiosInstance();
|
|
127
|
+
axiosInstance.post(`https://api-${node.confignode.server}/instanceSlotRequest`, {
|
|
128
|
+
'instancehash': node.confignode.instancehash,
|
|
129
|
+
'instanceauth': node.confignode.instanceauth,
|
|
130
|
+
'protocol': node.confignode.protocol,
|
|
131
|
+
'mountpath': RED.httpNode.mountpath,
|
|
132
|
+
'config': configStringBase64,
|
|
133
|
+
'version': commons.getNodeVersion()
|
|
134
|
+
})
|
|
135
|
+
.then(response => {
|
|
136
|
+
// Start SSH
|
|
137
|
+
const port = response.data.port;
|
|
138
|
+
node.log(`Using ${node.confignode.server} on port ${port}`);
|
|
139
|
+
startSSH(node, node.confignode.server, port);
|
|
140
|
+
resolve();
|
|
141
|
+
})
|
|
142
|
+
.catch((error) => {
|
|
143
|
+
// Log error
|
|
144
|
+
node.error('requestInstanceSlot: ' + commons.getNetworkErrorString(error))
|
|
145
|
+
if ( commons.getNetworkErrorCustomString(error) !== undefined) {
|
|
146
|
+
node.error(commons.getNetworkErrorCustomString(error));
|
|
147
|
+
}
|
|
122
148
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
149
|
+
// Wenn gebannt -> Merken
|
|
150
|
+
if ( error.response && error.response.status ) {
|
|
151
|
+
if ( error.response.status === 403 ) {
|
|
152
|
+
setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.banned"});
|
|
153
|
+
node.instanceIsBanned = true;
|
|
154
|
+
}
|
|
129
155
|
}
|
|
130
|
-
}
|
|
131
156
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
157
|
+
// Set status
|
|
158
|
+
if (!node.instanceIsBanned) setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.commerror"});
|
|
159
|
+
reject();
|
|
160
|
+
});
|
|
135
161
|
});
|
|
136
162
|
}
|
|
137
163
|
|
|
138
|
-
function tryConnect(node) {
|
|
164
|
+
async function tryConnect(node) {
|
|
139
165
|
// Request new instance slot and connect ssh
|
|
140
|
-
requestInstanceSlot(node)
|
|
166
|
+
await requestInstanceSlot(node)
|
|
141
167
|
|
|
142
168
|
// Create timer to check if ssh process still serving in 10 seconds
|
|
143
169
|
node.checkservingtimeout = setTimeout(checkServing, 1000*10, node);
|
|
@@ -193,8 +219,7 @@ module.exports = function(RED) {
|
|
|
193
219
|
if ( node.initialHeartbeatStatus === 'OK' ) {
|
|
194
220
|
// If the status before was ok > Restart communication
|
|
195
221
|
setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.heartbeaterror"});
|
|
196
|
-
|
|
197
|
-
node.serving = false
|
|
222
|
+
killSSHProcess(node)
|
|
198
223
|
node.log(`Heartbeat error. Reconnecting soon.`);
|
|
199
224
|
} else {
|
|
200
225
|
// If the status before had also an error > Just change the label.
|
|
@@ -213,12 +238,28 @@ module.exports = function(RED) {
|
|
|
213
238
|
}
|
|
214
239
|
}
|
|
215
240
|
|
|
241
|
+
function killSSHProcess(node) {
|
|
242
|
+
// Kill process
|
|
243
|
+
try {
|
|
244
|
+
if (node.sshprocess !== undefined) {
|
|
245
|
+
node.log(`Killing process ${node.sshprocess.pid}`);
|
|
246
|
+
node.sshprocess.kill();
|
|
247
|
+
}
|
|
248
|
+
node.serving = false
|
|
249
|
+
} catch (error) {
|
|
250
|
+
node.error(`Error in killSSHProcess: ${error}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
216
254
|
function RemoteAccessNode(config) {
|
|
217
255
|
RED.nodes.createNode(this,config);
|
|
218
256
|
const node = this;
|
|
257
|
+
node.sshprocess = undefined;
|
|
258
|
+
node.statustext = '';
|
|
219
259
|
node.serving = false;
|
|
220
260
|
node.verbose = config.verbose;
|
|
221
261
|
node.errorcounter = 0;
|
|
262
|
+
node.instanceIsBanned = false;
|
|
222
263
|
|
|
223
264
|
// Status
|
|
224
265
|
setStatus(node, {fill:"orange",shape:"dot",text:"remote-access.status.starting"});
|
|
@@ -238,18 +279,29 @@ module.exports = function(RED) {
|
|
|
238
279
|
return;
|
|
239
280
|
}
|
|
240
281
|
if ( node.confignode.instancehash !== undefined && node.confignode.instanceauth === undefined ) {
|
|
241
|
-
node.
|
|
282
|
+
node.error("Configuration has no instanceauth. Maybe restored backup!");
|
|
242
283
|
setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.backupconfig"});
|
|
243
284
|
return;
|
|
244
285
|
}
|
|
245
286
|
|
|
287
|
+
// Check if there are more access nodes for this config node
|
|
288
|
+
const accessNodeObject = instancehashToAccessNode[node.confignode.instancehash];
|
|
289
|
+
if (accessNodeObject !== undefined && accessNodeObject['id'] !== node.id) {
|
|
290
|
+
const errorText = `The config node '${node.confignode.name}' is already used in the access node '${accessNodeObject['name']}'. Use only one access node for a config node.`;
|
|
291
|
+
node.error(errorText);
|
|
292
|
+
setStatus(node, {fill:"red",shape:"dot",text:errorText});
|
|
293
|
+
return;
|
|
294
|
+
} else {
|
|
295
|
+
instancehashToAccessNode[node.confignode.instancehash] = { id: node.id, name: node.name }
|
|
296
|
+
}
|
|
297
|
+
|
|
246
298
|
// Init heartbeat
|
|
247
299
|
node.initialHeartbeatStatus = ''
|
|
248
300
|
node.lastHeartbeatStatus = ''
|
|
249
301
|
node.heartbeatinterval = setInterval(heartbeat, 5*60*1000, node);
|
|
250
302
|
|
|
251
303
|
// Call API for announce the instacehash and authentication, retrive server and port.
|
|
252
|
-
tryConnect(node)
|
|
304
|
+
tryConnect(node)
|
|
253
305
|
|
|
254
306
|
// Post URL for action
|
|
255
307
|
const postUrlAction = `/contrib-remote/action/${node.confignode.instancehash}`;
|
|
@@ -305,31 +357,48 @@ module.exports = function(RED) {
|
|
|
305
357
|
});
|
|
306
358
|
|
|
307
359
|
// Clean up on close
|
|
308
|
-
node.on('close', function() {
|
|
360
|
+
node.on('close', function(removed, done) {
|
|
361
|
+
// Set status
|
|
362
|
+
setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopping"});
|
|
363
|
+
|
|
364
|
+
// Kill process
|
|
365
|
+
killSSHProcess(node)
|
|
366
|
+
|
|
309
367
|
// Cancel timeouts
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
368
|
+
try {
|
|
369
|
+
clearTimeout(node.checkservingtimeout);
|
|
370
|
+
clearTimeout(node.tryconnecttimeout);
|
|
371
|
+
clearInterval(node.heartbeatinterval);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
node.error(`Error in close > clearTimeout: ${error}`);
|
|
374
|
+
}
|
|
313
375
|
|
|
314
376
|
// Remove old routes, without a new deploy would break it..
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
377
|
+
try {
|
|
378
|
+
RED.httpNode._router.stack.forEach(function(route,i,routes) {
|
|
379
|
+
if (route.route && route.route.path === postUrlAction) {
|
|
380
|
+
node.log(` -> Remove Route '${route.route.path}'`);
|
|
381
|
+
routes.splice(i,1);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
RED.httpNode._router.stack.forEach(function(route,i,routes) {
|
|
385
|
+
if (route.route && route.route.path === postUrlGeofence) {
|
|
386
|
+
node.log(` -> Remove Route '${route.route.path}'`);
|
|
387
|
+
routes.splice(i,1);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
RED.httpNode._router.stack.forEach(function(route,i,routes) {
|
|
391
|
+
if (route.route && route.route.path === getUrl) {
|
|
392
|
+
node.log(` -> Remove Route '${route.route.path}'`);
|
|
393
|
+
routes.splice(i,1);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
} catch (error) {
|
|
397
|
+
node.error(`Error in close > remove routs: ${error}`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Signal close done
|
|
401
|
+
done();
|
|
333
402
|
});
|
|
334
403
|
|
|
335
404
|
}
|
package/nodes/remote-commons.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const https = require('https')
|
|
2
|
-
const axios = require('axios')
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const axios = require('axios');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const path = require('path');
|
|
@@ -18,7 +18,26 @@ module.exports = {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
|
-
return axios.create({
|
|
21
|
+
return axios.create({
|
|
22
|
+
httpsAgent: httpsAgent,
|
|
23
|
+
timeout: 30000,
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json'
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
testUrlAvailable: async function(url) {
|
|
31
|
+
const httpsAgent = new https.Agent({
|
|
32
|
+
rejectUnauthorized: false,
|
|
33
|
+
});
|
|
34
|
+
const axiosInstance = axios.create({ httpsAgent: httpsAgent, timeout: 2000 });
|
|
35
|
+
try {
|
|
36
|
+
let responseUi = await axiosInstance.get(url);
|
|
37
|
+
if (responseUi.status >= 200 && responseUi.status <= 399) return true;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
22
41
|
},
|
|
23
42
|
|
|
24
43
|
getNodeVersion: function() {
|
package/nodes/remote-config.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
defaults: {
|
|
6
6
|
name: {value:"Node-RED UI"},
|
|
7
7
|
host: {value:"localhost"},
|
|
8
|
-
protocol: {value:"
|
|
8
|
+
protocol: {value:""},
|
|
9
9
|
port: {value:0, validate:RED.validators.number()},
|
|
10
10
|
baseurl: {value:""},
|
|
11
11
|
instancehash: {value:""},
|
|
@@ -48,11 +48,21 @@
|
|
|
48
48
|
if (document.getElementById('node-config-input-port').value === '') {
|
|
49
49
|
document.getElementById('node-config-input-port').value = data.port
|
|
50
50
|
}
|
|
51
|
+
if (document.getElementById('node-config-input-protocol').value === '') {
|
|
52
|
+
document.getElementById('node-config-input-protocol').value = data.protocol
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
});
|
|
52
56
|
|
|
53
|
-
// Show backup info?
|
|
57
|
+
// Show change info / backup info?
|
|
54
58
|
const instancehash = document.getElementById('node-config-input-instancehash').value;
|
|
55
59
|
const instanceauth = document.getElementById('node-config-input-instanceauth').value;
|
|
60
|
+
if (instancehash !== "" && instanceauth !== "") {
|
|
61
|
+
document.getElementById('changeInfo').style.visibility = 'visible';
|
|
62
|
+
document.getElementById('backupInfo').style.marginBottom = '20px';
|
|
63
|
+
document.getElementById('changeInfo').style.padding = '10px';
|
|
64
|
+
document.getElementById('changeInfo').style.removeProperty('max-height');
|
|
65
|
+
}
|
|
56
66
|
if (instancehash !== "" && instanceauth === "") {
|
|
57
67
|
document.getElementById('backupInfo').style.visibility = 'visible';
|
|
58
68
|
document.getElementById('backupInfo').style.marginTop = '40px';
|
|
@@ -119,6 +129,7 @@
|
|
|
119
129
|
const server = document.getElementById('node-config-input-server').value;
|
|
120
130
|
const name = document.getElementById('node-config-input-name').value;
|
|
121
131
|
const host = document.getElementById('node-config-input-host').value;
|
|
132
|
+
const protocol = document.getElementById('node-config-input-protocol').value;
|
|
122
133
|
const localport = document.getElementById('node-config-input-port').value;
|
|
123
134
|
const baseurl = document.getElementById('node-config-input-baseurl').value;
|
|
124
135
|
|
|
@@ -128,6 +139,7 @@
|
|
|
128
139
|
'server': server,
|
|
129
140
|
'name': name,
|
|
130
141
|
'host': host,
|
|
142
|
+
'protocol': protocol,
|
|
131
143
|
'localport': localport,
|
|
132
144
|
'baseurl': baseurl
|
|
133
145
|
}
|
|
@@ -168,21 +180,30 @@
|
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
function
|
|
183
|
+
function registerButtonClicked() {
|
|
184
|
+
// Get maybe existing values
|
|
185
|
+
const instancehash = document.getElementById('node-config-input-instancehash').value;
|
|
186
|
+
const instanceauth = document.getElementById('node-config-input-instanceauth').value;
|
|
187
|
+
|
|
188
|
+
// Check for backup
|
|
189
|
+
if (instancehash !== "" && instanceauth === "") {
|
|
190
|
+
alert(document.getElementById('backupInfoLabel').innerHTML);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
172
194
|
// Disable Button
|
|
173
|
-
document.getElementById('
|
|
195
|
+
document.getElementById('registerRemoteAppButton').disabled = true;
|
|
174
196
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
alert(data);
|
|
178
|
-
} else {
|
|
179
|
-
// Show error
|
|
180
|
-
alert('Error while registering app: ' + data.error);
|
|
181
|
-
}
|
|
197
|
+
// Remove last QR-Code
|
|
198
|
+
document.getElementById('registerRemoteAppQRCode').src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==";
|
|
182
199
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
200
|
+
// Request InstanceHash when empty
|
|
201
|
+
if ( instancehash === undefined || instancehash === '' ) {
|
|
202
|
+
registerInstance();
|
|
203
|
+
} else {
|
|
204
|
+
// Register app
|
|
205
|
+
registerApp();
|
|
206
|
+
}
|
|
186
207
|
}
|
|
187
208
|
|
|
188
209
|
function openSupportMailButtonClicked() {
|
|
@@ -195,8 +216,12 @@
|
|
|
195
216
|
</script>
|
|
196
217
|
|
|
197
218
|
<script type="text/html" data-template-name="remote-config">
|
|
198
|
-
<div id="
|
|
199
|
-
<label data-i18n="remote-config.label.
|
|
219
|
+
<div id="changeInfo" style="visibility: collapse; max-height: 0px; max-width: 460px; border-width: 1px; border-style: dashed; border-color: #666;">
|
|
220
|
+
<label data-i18n="remote-config.label.change_info"></label>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div id="backupInfo" style="visibility: collapse; max-height: 0px; max-width: 460px; border-width: 1px; border-style: dashed; border-color: red;">
|
|
224
|
+
<label id="backupInfoLabel" data-i18n="remote-config.label.backup_info" style="font-weight: bold; color: red;"></label>
|
|
200
225
|
</div>
|
|
201
226
|
|
|
202
227
|
<div class="form-row">
|
|
@@ -218,8 +243,8 @@
|
|
|
218
243
|
<div class="form-row">
|
|
219
244
|
<label for="node-config-input-protocol"><i class="fa fa-tag"></i> <span data-i18n="remote-config.label.protocol"></label>
|
|
220
245
|
<select id="node-config-input-protocol">
|
|
221
|
-
|
|
222
|
-
|
|
246
|
+
<option value="http">http</option>
|
|
247
|
+
<option value="https">https</option>
|
|
223
248
|
</select>
|
|
224
249
|
</div>
|
|
225
250
|
<div class="form-row">
|
|
@@ -242,8 +267,6 @@
|
|
|
242
267
|
</div>
|
|
243
268
|
<label><i class="fa fa-info-circle"></i> <span data-i18n="remote-config.label.region_desc"></label>
|
|
244
269
|
|
|
245
|
-
<label><i class="fa fa-info-circle"></i> <span data-i18n="remote-config.label.change_info"></label>
|
|
246
|
-
|
|
247
270
|
<br>
|
|
248
271
|
<button id="registerRemoteAppButton" type="button" class="red-ui-button" onclick="registerButtonClicked()">
|
|
249
272
|
<span id="registerRemoteAppButtonText" data-i18n="remote-config.actions.register_app">
|
package/nodes/remote-config.js
CHANGED
|
@@ -94,14 +94,24 @@ module.exports = function(RED) {
|
|
|
94
94
|
res.json(testResults);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
RED.httpAdmin.get("/contrib-remote/connectionData", RED.auth.needsPermission('remote-config.read'), function(req,res) {
|
|
98
|
-
// Return the internal IP and the
|
|
97
|
+
RED.httpAdmin.get("/contrib-remote/connectionData", RED.auth.needsPermission('remote-config.read'), async function(req,res) {
|
|
98
|
+
// Return the internal IP, the port, the protocol and the calculated baseurl
|
|
99
99
|
const ipData = {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
ipv4: internalIp.v4.sync(),
|
|
101
|
+
port: (RED.settings.uiPort !== undefined) ? String(RED.settings.uiPort) : '1880',
|
|
102
|
+
protocol: (RED.settings.https !== undefined) ? 'https' : 'http',
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
|
|
105
|
+
// Check Dashbaord 2 url
|
|
106
|
+
const availableDashbaord = await commons.testUrlAvailable(`${ipData.protocol}://${ipData.ipv4}:${ipData.port}${RED.httpNode.mountpath}dashboard`);
|
|
107
|
+
if (availableDashbaord) {
|
|
108
|
+
ipData.baseurl = `${RED.httpNode.mountpath}dashboard`;
|
|
109
|
+
} else {
|
|
110
|
+
// Dashbaord 2 not founnd, use UI settings instead
|
|
111
|
+
ipData.baseurl = RED.httpNode.mountpath + ((RED.settings.ui !== undefined && RED.settings.ui.path !== undefined) ? RED.settings.ui.path : 'ui');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// console.log(`${JSON.stringify(ipData)}`);
|
|
105
115
|
res.json(ipData);
|
|
106
116
|
});
|
|
107
117
|
|
|
@@ -152,24 +162,22 @@ module.exports = function(RED) {
|
|
|
152
162
|
})
|
|
153
163
|
.then(response => {
|
|
154
164
|
var localip = req.body.host;
|
|
155
|
-
if (localip.toLowerCase() == 'localhost')
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
if (localip === undefined) {
|
|
159
|
-
localip = "";
|
|
160
|
-
}
|
|
165
|
+
if (localip.toLowerCase() == 'localhost') localip = internalIp.v4.sync();
|
|
166
|
+
if (localip === undefined) localip = "";
|
|
161
167
|
|
|
162
168
|
const qrCodeData = {
|
|
163
169
|
'name': req.body.name,
|
|
164
170
|
'server': req.body.server,
|
|
165
171
|
'localip': localip,
|
|
166
172
|
'localport': req.body.localport,
|
|
173
|
+
'localprotocol': req.body.protocol,
|
|
167
174
|
'baseurl': req.body.baseurl,
|
|
168
175
|
'instancehash': response.data.instancehash,
|
|
169
176
|
'apphash': response.data.apphash,
|
|
170
177
|
'password': response.data.password,
|
|
171
178
|
'customerhash': response.data.customerhash,
|
|
172
|
-
'nodeversion':
|
|
179
|
+
'nodeversion': 2.0,
|
|
180
|
+
'timestamp': Date.now()
|
|
173
181
|
};
|
|
174
182
|
const qrCodeString = JSON.stringify(qrCodeData);
|
|
175
183
|
const qrCodeStringBuffer = Buffer.from(qrCodeString);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-remote",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Remote-RED is an ecosystem to bring remote access, push notification and geofencing to Node-RED. There are also additional functions like directly answer on a notification and homescreen widgets.",
|
|
5
5
|
"author": "Thorsten Heilmann",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"axios": "^
|
|
16
|
+
"axios": "^1.7.2",
|
|
17
17
|
"internal-ip": "^6.1.0",
|
|
18
18
|
"limiter": "^1.1.5",
|
|
19
19
|
"path": "^0.12.7",
|