node-red-contrib-zwave-js 7.0.0-beta.1 → 7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # node-red-contrib-zwave-js Change Log
2
2
 
3
+ - 7.0.1
4
+
5
+ **Changes**
6
+ - Small optimisations to Express route cleanup.
7
+
8
+
3
9
  - 7.0.0
4
10
 
5
11
  **Breaking Changes**
@@ -13,12 +19,12 @@
13
19
  **New Features**
14
20
  - **VALUE_UPDATED**, **VALUE_NOTIFICATION**, **GET_VALUE_RESPONSE** and **VALUE_DB** now contain a **normalizedObject** property.
15
21
  This property aims to make it easy to utilise the value change, in that it summarises the event with easy to understand property names.
16
- - New UI Layout - We have overhauled the UI, and focused on making it fit more into the node red style guide.
17
- - An entierly new User Interface to render a Network Mesh Map.
22
+ - An entierly new side panel UI. This redesign aims to better align with the Node RED style guide.
23
+ - An entierly new Network Mesh Map, that is now based on routing header information instead of Node Neighbours.
18
24
  - Network statistics now include route information that is obtained during communication, and is used
19
25
  as the basis of the new map.
20
26
  - Multiple ZWave sticks/Networks are now supported (finally)
21
- - all message will contain a property of **networkId** to indentify the source network.
27
+ - All message will contain a property of **networkId** to indentify the source network.
22
28
  **WARNING**: Before adding another network - please ensure you open up the currently configured Controller
23
29
  and save it again (Remembering to deploy) - this will allign the current network to ID 1
24
30
  - Improved Recovery when the USB/ZWave transceiver has been removed/re-introduced.
@@ -28,7 +34,7 @@
28
34
  **Fixes**
29
35
  - Nodes that are not marked as ready can now be removed correctly.
30
36
  - Fix potential issue when **httpAdminRoot** has a custom value.
31
- - various performance, stability and optimisations to the UI.
37
+ - Various performance, stability and optimisations to the UI.
32
38
  - UI Interaction is no longer picked up by Device Nodes.
33
39
 
34
40
  **Changes**
package/README.md CHANGED
@@ -47,10 +47,10 @@ Included with the contrib is a [user interface](https://github.com/zwave-js/node
47
47
  ![Image](./GHImages/Demo.png)
48
48
 
49
49
  There are 4 node types included with this contrib ([click here](https://github.com/zwave-js/node-red-contrib-zwave-js/wiki/node-types) for full details about these nodes)
50
- - `zwave-js`: this node is used to set up a connection to your USB Z-Wave controller, set security keys, and manage various advanced controller options
51
- - `zwave-device`: this node is used to send and receive messages to one or more of the Z-Wave devices on your network
52
- - `event-filter`: this node is used to filter and sort messages from your Z-Wave devices
53
- - `cmd-factory`: this node simplifies creation of messages being sent to your Z-Wave devices
50
+ - `ZWave Controller`: this node is used to set up a connection to your USB Z-Wave controller, set security keys, and manage various advanced controller options
51
+ - `ZWave Device`: this node is used to send and receive messages to one or more of the Z-Wave devices on your network
52
+ - `Event Filter`: this node is used to filter and sort messages from your Z-Wave devices
53
+ - `CMD Factory`: this node simplifies creation of messages being sent to your Z-Wave devices
54
54
 
55
55
  ### Getting Started Links
56
56
  - [Installing](https://github.com/zwave-js/node-red-contrib-zwave-js/wiki/getting-started): system requirements and install instructions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-zwave-js",
3
- "version": "7.0.0-beta.1",
3
+ "version": "7.0.1",
4
4
  "license": "MIT",
5
5
  "description": "An extremely powerful, easy to use, and feature rich Z-Wave node for Node Red, based on Z-Wave JS.",
6
6
  "dependencies": {
@@ -9,10 +9,10 @@
9
9
  "serialport": "^10.4.0",
10
10
  "winston": "^3.7.2",
11
11
  "winston-transport": "^4.5.0",
12
- "zwave-js": "^9.0.7"
12
+ "zwave-js": "^9.2.1"
13
13
  },
14
14
  "devDependencies": {
15
- "eslint": "^8.14.0",
15
+ "eslint": "^8.15.0",
16
16
  "prettier": "^2.6.2"
17
17
  },
18
18
  "scripts": {
@@ -7,20 +7,35 @@
7
7
  <script src="https://unpkg.com/weaverjs@1.2.0/dist/weaver.min.js"></script>
8
8
  <script type="text/javascript" src="{BASE}/cytoscape-spread.js"></script>
9
9
  <script type="text/javascript" src="{BASE}/maprender.js"></script>
10
+
11
+ <style>
12
+ #Details {
13
+ width: 400px;
14
+ height: 400px;
15
+ background-color: rgb(241, 241, 241);
16
+ font-size: 14px;
17
+ font-family: 'Helvetica Neue Light', 'Calibiri Light', Roboto;
18
+ -webkit-border-radius: 6px;
19
+ -moz-border-radius: 6px;
20
+ border-radius: 6px;
21
+ border-style: solid;
22
+ border-width: 1px;
23
+ -webkit-box-shadow: 10px 10px 5px -7px rgba(0, 0, 0, 0.75);
24
+ -moz-box-shadow: 10px 10px 5px -7px rgba(0, 0, 0, 0.75);
25
+ box-shadow: 10px 10px 5px -7px rgba(0, 0, 0, 0.75);
26
+ padding: 10px;
27
+ box-sizing: content-box;
28
+ }
29
+ </style>
10
30
  </head>
11
31
  <body style="height: 100%">
12
32
  <div style="display: flex; width: 100%; height: 100%">
13
- <div
14
- id="Details"
15
- style="
16
- width: 400px;
17
- height: 100%;
18
- background-color: rgb(241, 241, 241);
19
- font-size: 16px;
20
- font-family: 'Helvetica Neue Light', 'Calibiri Light', Roboto;
21
- "
22
- >
23
- Select A node to view information.
33
+ <div id="Details">
34
+ <span
35
+ >Select A node to view information.<br /><br />NOTE: Disconnected
36
+ Nodes will have their routes determined, after they first
37
+ communicate.</span
38
+ >
24
39
  </div>
25
40
  <div
26
41
  id="NetworkMesh"
@@ -22,16 +22,22 @@ function Render(Base) {
22
22
  if (N.controller) {
23
23
  const EL = {
24
24
  data: {
25
+ isController: N.controller,
25
26
  id: N.nodeId,
26
27
  name: 'Controller',
28
+ nameOnly: `Controller`,
29
+ location: `Hopefully away from interference.`,
27
30
  fontSize: '12px',
28
- icon: `${Base}/Stick.png`
31
+ icon: `${Base}/Stick.png`,
32
+ powerSource: { type: 'bus' },
33
+ statistics: N.statistics
29
34
  }
30
35
  };
31
36
  Elements.push(EL);
32
37
  } else {
33
38
  const EL = {
34
39
  data: {
40
+ isController: N.controller,
35
41
  id: N.nodeId,
36
42
  name: `${N.nodeId} - ${N.name || 'No Name'}`,
37
43
  nameOnly: `${N.name || 'No Name'}`,
@@ -160,20 +166,31 @@ function LoadData() {
160
166
  const NodePath = this.data('path');
161
167
 
162
168
  Mesh.edges().css({ lineColor: '#ededed', 'target-arrow-color': '#f1f1f1' });
163
- let MST = 1;
164
- NodePath.forEach((PS) => {
165
- const Color =
166
- Mesh.edges("[id='" + PS + "']").data().target === '1' ? 'green' : 'black';
167
169
 
168
- setTimeout(() => {
169
- Mesh.edges("[id='" + PS + "']").css({
170
- lineColor: Color,
171
- zIndex: 100,
172
- 'target-arrow-color': 'black'
173
- });
174
- }, 150 * MST);
175
- MST++;
176
- });
170
+ if (NodePath !== undefined) {
171
+ Mesh.edges().css({ lineColor: '#ededed', 'target-arrow-color': '#f1f1f1' });
172
+ let MST = 1;
173
+ NodePath.forEach((PS) => {
174
+ const Color =
175
+ Mesh.edges("[id='" + PS + "']").data().target === '1'
176
+ ? 'green'
177
+ : 'black';
178
+
179
+ setTimeout(() => {
180
+ Mesh.edges("[id='" + PS + "']").css({
181
+ lineColor: Color,
182
+ zIndex: 100,
183
+ 'target-arrow-color': 'black'
184
+ });
185
+ }, 150 * MST);
186
+ MST++;
187
+ });
188
+ } else {
189
+ Mesh.edges("[target='1']").css({
190
+ lineColor: 'green',
191
+ 'target-arrow-color': 'black'
192
+ });
193
+ }
177
194
 
178
195
  // Node
179
196
  const Node = $('<li>');
@@ -245,21 +262,53 @@ function LoadData() {
245
262
 
246
263
  const NetStatsDetails = $('<ul>');
247
264
  NetStatsDetails.appendTo(NetStats);
248
- $(`<li>Commands TX: ${this.data('statistics').commandsTX}</li>`).appendTo(
249
- NetStatsDetails
250
- );
251
- $(
252
- `<li>Commands TXD: ${this.data('statistics').commandsDroppedTX}</li>`
253
- ).appendTo(NetStatsDetails);
254
- $(`<li>Commands RX: ${this.data('statistics').commandsRX}</li>`).appendTo(
255
- NetStatsDetails
256
- );
257
- $(
258
- `<li>Commands RXD: ${this.data('statistics').commandsDroppedRX}</li>`
259
- ).appendTo(NetStatsDetails);
260
- $(`<li>Timeouts: ${this.data('statistics').timeoutResponse}</li>`).appendTo(
261
- NetStatsDetails
262
- );
265
+
266
+ if (this.data('isController')) {
267
+ $(`<li>Messages TX: ${this.data('statistics').messagesTX}</li>`).appendTo(
268
+ NetStatsDetails
269
+ );
270
+ $(
271
+ `<li>Messages Dropped TX: ${
272
+ this.data('statistics').messagesDroppedTX
273
+ }</li>`
274
+ ).appendTo(NetStatsDetails);
275
+
276
+ $(`<li>Messages RX: ${this.data('statistics').messagesRX}</li>`).appendTo(
277
+ NetStatsDetails
278
+ );
279
+ $(
280
+ `<li>Messages Dropped RX: ${
281
+ this.data('statistics').messagesDroppedRX
282
+ }</li>`
283
+ ).appendTo(NetStatsDetails);
284
+ $(`<li>NAK: ${this.data('statistics').NAK}</li>`).appendTo(NetStatsDetails);
285
+ $(`<li>CAN: ${this.data('statistics').CAN}</li>`).appendTo(NetStatsDetails);
286
+ $(`<li>ACK Timeout: ${this.data('statistics').timeoutACK}</li>`).appendTo(
287
+ NetStatsDetails
288
+ );
289
+ $(
290
+ `<li>Response Timeout: ${this.data('statistics').timeoutResponse}</li>`
291
+ ).appendTo(NetStatsDetails);
292
+ $(
293
+ `<li>Callback Timeout: ${this.data('statistics').timeoutCallback}</li>`
294
+ ).appendTo(NetStatsDetails);
295
+ } else {
296
+ $(`<li>Commands TX: ${this.data('statistics').commandsTX}</li>`).appendTo(
297
+ NetStatsDetails
298
+ );
299
+ $(
300
+ `<li>Commands TXD: ${this.data('statistics').commandsDroppedTX}</li>`
301
+ ).appendTo(NetStatsDetails);
302
+ $(`<li>Commands RX: ${this.data('statistics').commandsRX}</li>`).appendTo(
303
+ NetStatsDetails
304
+ );
305
+ $(
306
+ `<li>Commands RXD: ${this.data('statistics').commandsDroppedRX}</li>`
307
+ ).appendTo(NetStatsDetails);
308
+ $(`<li>Timeouts: ${this.data('statistics').timeoutResponse}</li>`).appendTo(
309
+ NetStatsDetails
310
+ );
311
+ }
263
312
 
264
313
  $('#Details').html(Data);
265
314
  }
@@ -67,6 +67,11 @@ const DCs = {
67
67
  name: 'getNodeStatistics',
68
68
  noWait: false
69
69
  },
70
+ getControllerStatistics: {
71
+ API: 'DriverAPI',
72
+ name: 'getControllerStatistics',
73
+ noWait: false
74
+ },
70
75
  checkKeyReq: {
71
76
  API: 'IEAPI',
72
77
  name: 'checkKeyReq',
@@ -258,10 +263,12 @@ const ZwaveJsUI = (function () {
258
263
  };
259
264
  }
260
265
 
261
- $('<div>')
266
+ const D = $('<div>')
262
267
  .css({ padding: 10, maxWidth: 500, wordWrap: 'break-word' })
263
268
  .html(message)
264
269
  .dialog(Options);
270
+
271
+ return D;
265
272
  }
266
273
 
267
274
  function ShowCommandViewer() {
@@ -531,6 +538,13 @@ const ZwaveJsUI = (function () {
531
538
  PL[2][0].endpoint = parseInt(EI.val());
532
539
  }
533
540
 
541
+ const D = modalPrompt(
542
+ 'Adding Association...',
543
+ 'Please wait.',
544
+ [],
545
+ false
546
+ );
547
+
534
548
  ControllerCMD(
535
549
  DCs.addAssociations.API,
536
550
  DCs.addAssociations.name,
@@ -539,9 +553,11 @@ const ZwaveJsUI = (function () {
539
553
  DCs.addAssociations.noWait
540
554
  )
541
555
  .then(() => {
556
+ D.dialog('destroy');
542
557
  GMGroupSelected();
543
558
  })
544
559
  .catch((err) => {
560
+ D.dialog('destroy');
545
561
  if (err.status === 504) {
546
562
  modalAlert(
547
563
  'The device maybe in a sleeping state, the association will be added later.',
@@ -574,6 +590,13 @@ const ZwaveJsUI = (function () {
574
590
 
575
591
  const Buttons = {
576
592
  Yes: function () {
593
+ const D = modalPrompt(
594
+ 'Removing Association',
595
+ 'Please wait.',
596
+ [],
597
+ false
598
+ );
599
+
577
600
  ControllerCMD(
578
601
  DCs.removeAssociations.API,
579
602
  DCs.removeAssociations.name,
@@ -582,9 +605,11 @@ const ZwaveJsUI = (function () {
582
605
  DCs.removeAssociations.noWait
583
606
  )
584
607
  .then(() => {
608
+ D.dialog('destroy');
585
609
  GMGroupSelected();
586
610
  })
587
611
  .catch((err) => {
612
+ D.dialog('destroy');
588
613
  if (err.status === 504) {
589
614
  modalAlert(
590
615
  'The device maybe in a sleeping state, the association will be removed later.',
@@ -751,7 +776,20 @@ const ZwaveJsUI = (function () {
751
776
  _Nodes.push(_Node);
752
777
  });
753
778
 
754
- res(_Nodes);
779
+ ControllerCMD(
780
+ DCs.getControllerStatistics.API,
781
+ DCs.getControllerStatistics.name,
782
+ undefined,
783
+ undefined,
784
+ DCs.getControllerStatistics.noWait
785
+ )
786
+ .then(({ object }) => {
787
+ _Nodes.filter((_PC) => _PC.controller)[0].statistics = object;
788
+ res(_Nodes);
789
+ })
790
+ .catch((err) => {
791
+ rej(err.responseText);
792
+ });
755
793
  })
756
794
  .catch((err) => {
757
795
  rej(err.responseText);
@@ -839,6 +877,10 @@ const ZwaveJsUI = (function () {
839
877
  if (NoTimeoutFor.includes(method)) {
840
878
  Options.timeout = 0;
841
879
  Payload.noTimeout = true;
880
+ } else {
881
+ // Hopefully we will never have to depend on this, if so - there is something seriously wrong with the network, that the user should resolve.
882
+ // Out internal timeouts of 10s will see to anything driver/server related
883
+ Options.timeout = 30000;
842
884
  }
843
885
 
844
886
  if (mode !== 'IEAPI') {
@@ -949,13 +991,13 @@ const ZwaveJsUI = (function () {
949
991
  } else {
950
992
  Nodes.forEach((N) => $('#zwave-js-node-list').append(renderNode(N)));
951
993
  }
994
+
995
+ $('#zwave-js-node-properties').treeList('empty');
952
996
  })
953
997
  .catch((err) => {
954
998
  modalAlert(err.responseText, 'Could not fetch nodes.');
955
999
  throw new Error(err.responseText);
956
1000
  });
957
-
958
- $('#zwave-js-node-properties > div > div > div > ol').empty();
959
1001
  }
960
1002
 
961
1003
  function sortByKey(obj) {
@@ -1553,6 +1595,12 @@ const ZwaveJsUI = (function () {
1553
1595
  }
1554
1596
  const Buttons = {
1555
1597
  'Yes - Remove': function () {
1598
+ const D = modalPrompt(
1599
+ 'Checking if Node has failed...',
1600
+ 'Please wait.',
1601
+ [],
1602
+ false
1603
+ );
1556
1604
  Removing = true;
1557
1605
  ControllerCMD(
1558
1606
  DCs.removeFailedNode.API,
@@ -1562,10 +1610,12 @@ const ZwaveJsUI = (function () {
1562
1610
  DCs.removeFailedNode.noWait
1563
1611
  )
1564
1612
  .catch((err) => {
1613
+ D.dialog('destroy');
1565
1614
  modalAlert(err.responseText, 'Could not remove the Node.');
1566
1615
  Removing = false;
1567
1616
  })
1568
1617
  .then(() => {
1618
+ D.dialog('destroy');
1569
1619
  Removing = false;
1570
1620
  });
1571
1621
  }
@@ -1769,8 +1819,10 @@ const ZwaveJsUI = (function () {
1769
1819
  });
1770
1820
  BA.appendTo(CTTD3);
1771
1821
 
1822
+ // Group
1772
1823
  const Group = $('<button>');
1773
1824
  Group.click(() => {
1825
+ IsDriverReady();
1774
1826
  if (GroupedNodes) {
1775
1827
  GroupedNodes = false;
1776
1828
  Group.find('i').removeClass('fa-indent');
@@ -2256,213 +2308,206 @@ const ZwaveJsUI = (function () {
2256
2308
  }
2257
2309
 
2258
2310
  function AddOverlayNodeButtons(Node, Row) {
2259
- if (Node === undefined && Row === undefined) {
2260
- if (BA !== undefined) {
2261
- BA.css({
2262
- display: 'none'
2263
- });
2264
- }
2311
+ HoveredNode = Node;
2312
+
2313
+ Row.children().css({ display: 'none' });
2314
+ Row.children().first().css({ display: 'block' });
2315
+ Row.css({ height: '30px' });
2316
+
2317
+ if (BA === undefined) {
2318
+ BA = $('<div>');
2319
+ BA.css({
2320
+ position: 'relative',
2321
+ left: '-1px',
2322
+ backgroundColor: 'inherit'
2323
+ });
2324
+
2325
+ const NameLocation = $('<button>');
2326
+ NameLocation.click(() => {
2327
+ event.stopPropagation();
2328
+ IsNodeReady(HoveredNode);
2329
+ NameNode();
2330
+ });
2331
+ NameLocation.addClass(
2332
+ 'red-ui-button red-ui-button-small zwave-js-round-square'
2333
+ );
2334
+ NameLocation.css({
2335
+ width: '30px',
2336
+ height: '30px',
2337
+ marginRight: '1px'
2338
+ });
2339
+ NameLocation.append('<i class="fa fa-pencil fa-lg"></i>');
2340
+ RED.popover.tooltip(NameLocation, 'Edit Name / Location');
2341
+ BA.append(NameLocation);
2342
+
2343
+ const _HealthCheck = $('<button>');
2344
+ _HealthCheck.click(() => {
2345
+ event.stopPropagation();
2346
+ IsNodeReady(HoveredNode);
2347
+ HealthCheck();
2348
+ });
2349
+ _HealthCheck.addClass(
2350
+ 'red-ui-button red-ui-button-small zwave-js-round-square'
2351
+ );
2352
+ _HealthCheck.css({
2353
+ width: '30px',
2354
+ height: '30px',
2355
+ marginRight: '1px'
2356
+ });
2357
+ _HealthCheck.append('<i class="fa fa-stethoscope fa-lg"></i>');
2358
+ RED.popover.tooltip(_HealthCheck, 'Run Health Check');
2359
+ BA.append(_HealthCheck);
2360
+
2361
+ const Heal = $('<button>');
2362
+ Heal.click(() => {
2363
+ event.stopPropagation();
2364
+ IsNodeReady(HoveredNode);
2365
+ StartNodeHeal();
2366
+ });
2367
+ Heal.addClass('red-ui-button red-ui-button-small zwave-js-round-square');
2368
+ Heal.css({
2369
+ width: '30px',
2370
+ height: '30px',
2371
+ marginRight: '1px'
2372
+ });
2373
+ Heal.append('<i class="fa fa-medkit fa-lg"></i>');
2374
+ RED.popover.tooltip(Heal, 'Heal Node');
2375
+ BA.append(Heal);
2376
+
2377
+ const Associations = $('<button>');
2378
+ Associations.click(() => {
2379
+ event.stopPropagation();
2380
+ IsNodeReady(HoveredNode);
2381
+ AssociationMGMT();
2382
+ });
2383
+ Associations.addClass(
2384
+ 'red-ui-button red-ui-button-small zwave-js-round-square'
2385
+ );
2386
+ Associations.css({
2387
+ width: '30px',
2388
+ height: '30px',
2389
+ marginRight: '1px'
2390
+ });
2391
+ Associations.append('<i class="fa fa-code-fork fa-lg"></i>');
2392
+ RED.popover.tooltip(Associations, 'Association Management');
2393
+ BA.append(Associations);
2394
+
2395
+ const Interview = $('<button>');
2396
+ Interview.click(() => {
2397
+ event.stopPropagation();
2398
+ IsNodeReady(HoveredNode);
2399
+ InterviewNode();
2400
+ });
2401
+ Interview.addClass(
2402
+ 'red-ui-button red-ui-button-small zwave-js-round-square'
2403
+ );
2404
+ Interview.css({
2405
+ width: '30px',
2406
+ height: '30px',
2407
+ marginRight: '1px'
2408
+ });
2409
+ Interview.append('<i class="fa fa-handshake-o fa-lg"></i>');
2410
+ RED.popover.tooltip(Interview, 'Re-Interview Node');
2411
+ BA.append(Interview);
2412
+
2413
+ const RemoveFailed = $('<button>');
2414
+ RemoveFailed.click(() => {
2415
+ event.stopPropagation();
2416
+ RemoveFailedNode();
2417
+ });
2418
+ RemoveFailed.addClass(
2419
+ 'red-ui-button red-ui-button-small zwave-js-round-square'
2420
+ );
2421
+ RemoveFailed.css({
2422
+ width: '30px',
2423
+ height: '30px',
2424
+ marginRight: '1px'
2425
+ });
2426
+ RemoveFailed.append('<i class="fa fa-trash-o fa-lg"></i>');
2427
+ RED.popover.tooltip(RemoveFailed, 'Remove Failed Node');
2428
+ BA.append(RemoveFailed);
2429
+
2430
+ const ReplaceFailed = $('<button>');
2431
+ ReplaceFailed.click(() => {
2432
+ event.stopPropagation();
2433
+ ShowReplacePrompt();
2434
+ });
2435
+ ReplaceFailed.addClass(
2436
+ 'red-ui-button red-ui-button-small zwave-js-round-square'
2437
+ );
2438
+ ReplaceFailed.css({
2439
+ width: '30px',
2440
+ height: '30px',
2441
+ marginRight: '1px'
2442
+ });
2443
+ ReplaceFailed.append('<i class="fa fa-chain-broken fa-lg"></i>');
2444
+ RED.popover.tooltip(ReplaceFailed, 'Replace Failed Node');
2445
+ BA.append(ReplaceFailed);
2446
+
2447
+ const _OpenDB = $('<button>');
2448
+ _OpenDB.click(() => {
2449
+ IsNodeReady(HoveredNode);
2450
+ OpenDB();
2451
+ });
2452
+ _OpenDB.addClass(
2453
+ 'red-ui-button red-ui-button-small zwave-js-round-square'
2454
+ );
2455
+ _OpenDB.css({
2456
+ width: '30px',
2457
+ height: '30px',
2458
+ marginRight: '1px'
2459
+ });
2460
+ _OpenDB.append('<i class="fa fa-database fa-lg"></i>');
2461
+ RED.popover.tooltip(_OpenDB, 'Open in Device Browser');
2462
+ BA.append(_OpenDB);
2265
2463
  } else {
2266
- HoveredNode = Node;
2267
-
2268
- Row.children().css({ display: 'none' });
2269
- Row.children().first().css({ display: 'block' });
2270
- Row.css({ height: '30px' });
2271
-
2272
- if (BA === undefined) {
2273
- BA = $('<div>');
2274
- BA.css({
2275
- position: 'relative',
2276
- left: '-1px',
2277
- backgroundColor: 'inherit'
2278
- });
2464
+ BA.css({
2465
+ display: 'block'
2466
+ });
2467
+ }
2279
2468
 
2280
- const NameLocation = $('<button>');
2281
- NameLocation.click(() => {
2282
- event.stopPropagation();
2283
- IsNodeReady(HoveredNode);
2284
- NameNode();
2285
- });
2286
- NameLocation.addClass(
2287
- 'red-ui-button red-ui-button-small zwave-js-round-square'
2288
- );
2289
- NameLocation.css({
2290
- width: '30px',
2291
- height: '30px',
2292
- marginRight: '1px'
2293
- });
2294
- NameLocation.append('<i class="fa fa-pencil fa-lg"></i>');
2295
- RED.popover.tooltip(NameLocation, 'Edit Name / Location');
2296
- BA.append(NameLocation);
2297
-
2298
- const _HealthCheck = $('<button>');
2299
- _HealthCheck.click(() => {
2300
- event.stopPropagation();
2301
- IsNodeReady(HoveredNode);
2302
- HealthCheck();
2303
- });
2304
- _HealthCheck.addClass(
2305
- 'red-ui-button red-ui-button-small zwave-js-round-square'
2306
- );
2307
- _HealthCheck.css({
2308
- width: '30px',
2309
- height: '30px',
2310
- marginRight: '1px'
2311
- });
2312
- _HealthCheck.append('<i class="fa fa-stethoscope fa-lg"></i>');
2313
- RED.popover.tooltip(_HealthCheck, 'Run Health Check');
2314
- BA.append(_HealthCheck);
2315
-
2316
- const Heal = $('<button>');
2317
- Heal.click(() => {
2318
- event.stopPropagation();
2319
- IsNodeReady(HoveredNode);
2320
- StartNodeHeal();
2321
- });
2322
- Heal.addClass(
2323
- 'red-ui-button red-ui-button-small zwave-js-round-square'
2324
- );
2325
- Heal.css({
2326
- width: '30px',
2327
- height: '30px',
2328
- marginRight: '1px'
2329
- });
2330
- Heal.append('<i class="fa fa-medkit fa-lg"></i>');
2331
- RED.popover.tooltip(Heal, 'Heal Node');
2332
- BA.append(Heal);
2333
-
2334
- const Associations = $('<button>');
2335
- Associations.click(() => {
2336
- event.stopPropagation();
2337
- IsNodeReady(HoveredNode);
2338
- AssociationMGMT();
2339
- });
2340
- Associations.addClass(
2341
- 'red-ui-button red-ui-button-small zwave-js-round-square'
2342
- );
2343
- Associations.css({
2344
- width: '30px',
2345
- height: '30px',
2346
- marginRight: '1px'
2347
- });
2348
- Associations.append('<i class="fa fa-code-fork fa-lg"></i>');
2349
- RED.popover.tooltip(Associations, 'Association Management');
2350
- BA.append(Associations);
2351
-
2352
- const Interview = $('<button>');
2353
- Interview.click(() => {
2354
- event.stopPropagation();
2355
- IsNodeReady(HoveredNode);
2356
- InterviewNode();
2357
- });
2358
- Interview.addClass(
2359
- 'red-ui-button red-ui-button-small zwave-js-round-square'
2360
- );
2361
- Interview.css({
2362
- width: '30px',
2363
- height: '30px',
2364
- marginRight: '1px'
2365
- });
2366
- Interview.append('<i class="fa fa-handshake-o fa-lg"></i>');
2367
- RED.popover.tooltip(Interview, 'Re-Interview Node');
2368
- BA.append(Interview);
2369
-
2370
- const RemoveFailed = $('<button>');
2371
- RemoveFailed.click(() => {
2372
- event.stopPropagation();
2373
- RemoveFailedNode();
2374
- });
2375
- RemoveFailed.addClass(
2376
- 'red-ui-button red-ui-button-small zwave-js-round-square'
2377
- );
2378
- RemoveFailed.css({
2379
- width: '30px',
2380
- height: '30px',
2381
- marginRight: '1px'
2382
- });
2383
- RemoveFailed.append('<i class="fa fa-trash-o fa-lg"></i>');
2384
- RED.popover.tooltip(RemoveFailed, 'Remove Failed Node');
2385
- BA.append(RemoveFailed);
2386
-
2387
- const ReplaceFailed = $('<button>');
2388
- ReplaceFailed.click(() => {
2389
- event.stopPropagation();
2390
- ShowReplacePrompt();
2391
- });
2392
- ReplaceFailed.addClass(
2393
- 'red-ui-button red-ui-button-small zwave-js-round-square'
2394
- );
2395
- ReplaceFailed.css({
2396
- width: '30px',
2397
- height: '30px',
2398
- marginRight: '1px'
2399
- });
2400
- ReplaceFailed.append('<i class="fa fa-chain-broken fa-lg"></i>');
2401
- RED.popover.tooltip(ReplaceFailed, 'Replace Failed Node');
2402
- BA.append(ReplaceFailed);
2403
-
2404
- const _OpenDB = $('<button>');
2405
- _OpenDB.click(() => {
2406
- IsNodeReady(HoveredNode);
2407
- OpenDB();
2408
- });
2409
- _OpenDB.addClass(
2410
- 'red-ui-button red-ui-button-small zwave-js-round-square'
2411
- );
2412
- _OpenDB.css({
2413
- width: '30px',
2414
- height: '30px',
2415
- marginRight: '1px'
2416
- });
2417
- _OpenDB.append('<i class="fa fa-database fa-lg"></i>');
2418
- RED.popover.tooltip(_OpenDB, 'Open in Device Browser');
2419
- BA.append(_OpenDB);
2420
- } else {
2421
- BA.css({
2422
- display: 'block'
2423
- });
2424
- }
2469
+ Row.append(BA);
2470
+ }
2425
2471
 
2426
- Row.append(BA);
2472
+ function ResetBA() {
2473
+ if (LastTargetForBA !== undefined) {
2474
+ LastTargetForBA.children().css({ display: 'block' });
2475
+ }
2476
+ if (BA !== undefined) {
2477
+ BA.css({
2478
+ display: 'none'
2479
+ });
2427
2480
  }
2428
2481
  }
2429
2482
 
2430
2483
  function renderNode(node) {
2431
- let Timer;
2432
-
2433
2484
  return $('<div>')
2434
2485
  .addClass('red-ui-treeList-label zwave-js-node-row')
2435
2486
  .attr('data-nodeid', node.nodeId)
2436
2487
  .data('info', node)
2437
- .dblclick(() => {
2438
- if (LastTargetForBA !== undefined)
2439
- LastTargetForBA.children().css({ display: 'block' });
2440
-
2441
- const Row = $('.zwave-js-node-row[data-nodeid="' + node.nodeId + '"]');
2442
- AddOverlayNodeButtons(node, $(Row));
2443
- LastTargetForBA = $(Row);
2444
-
2445
- clearTimeout(Timer);
2446
- Timer = undefined;
2447
- })
2448
- .click(() => {
2488
+ .click((e) => {
2449
2489
  if (selectedNode === node.nodeId) {
2490
+ ResetBA();
2450
2491
  const Row = $(
2451
2492
  '.zwave-js-node-row[data-nodeid="' + node.nodeId + '"]'
2452
2493
  );
2453
2494
  AddOverlayNodeButtons(node, $(Row));
2454
2495
  LastTargetForBA = $(Row);
2455
2496
  return;
2497
+ } else {
2498
+ if (e.shiftKey) {
2499
+ ResetBA();
2500
+ const Row = $(
2501
+ '.zwave-js-node-row[data-nodeid="' + node.nodeId + '"]'
2502
+ );
2503
+ AddOverlayNodeButtons(node, $(Row));
2504
+ LastTargetForBA = $(Row);
2505
+ } else {
2506
+ ResetBA();
2507
+ IsNodeReady(node);
2508
+ selectNode(node.nodeId);
2509
+ }
2456
2510
  }
2457
- clearTimeout(Timer);
2458
- Timer = undefined;
2459
- Timer = setTimeout(() => {
2460
- if (LastTargetForBA !== undefined)
2461
- LastTargetForBA.children().css({ display: 'block' });
2462
- AddOverlayNodeButtons();
2463
- IsNodeReady(node);
2464
- selectNode(node.nodeId);
2465
- }, 250);
2466
2511
  })
2467
2512
  .append(
2468
2513
  $('<div>').html(node.nodeId).addClass('zwave-js-node-row-id'),
@@ -2636,10 +2681,7 @@ const ZwaveJsUI = (function () {
2636
2681
 
2637
2682
  function updateNodeFetchStatus(text) {
2638
2683
  $('#zwave-js-node-properties').treeList('data', [
2639
- {
2640
- label: text,
2641
- class: 'zwave-js-node-fetch-status'
2642
- }
2684
+ { label: text, class: 'zwave-js-node-fetch-status' }
2643
2685
  ]);
2644
2686
  }
2645
2687
 
@@ -265,7 +265,7 @@ class UIServer {
265
265
  const timeout = setTimeout(() => {
266
266
  TimedOut = true;
267
267
  res.status(504).end();
268
- }, 5000);
268
+ }, 10000);
269
269
 
270
270
  if (req.body.noTimeout) {
271
271
  clearTimeout(timeout);
@@ -457,19 +457,20 @@ class UIServer {
457
457
  delete this._Context.controller;
458
458
  delete this._Context.input;
459
459
  } else {
460
- const Routes = [];
461
- this._RED.httpAdmin._router.stack.forEach((R) => {
462
- if (R.route === undefined) {
463
- Routes.push(R);
464
- return;
460
+ const Check = (Route) => {
461
+ if (Route.route === undefined) {
462
+ return true;
465
463
  }
466
- if (!R.route.path.startsWith(`/zwave-js/${this._NetworkIdentifier}`)) {
467
- Routes.push(R);
468
- return;
464
+ if (
465
+ !Route.route.path.startsWith(`/zwave-js/${this._NetworkIdentifier}`)
466
+ ) {
467
+ return true;
469
468
  }
470
- });
471
469
 
472
- this._RED.httpAdmin._router.stack = Routes;
470
+ return false;
471
+ };
472
+ this._RED.httpAdmin._router.stack =
473
+ this._RED.httpAdmin._router.stack.filter(Check);
473
474
 
474
475
  delete this._Context.controller;
475
476
  delete this._Context.input;
@@ -81,32 +81,22 @@ function ParseCode(req, res) {
81
81
  }
82
82
  }
83
83
 
84
- const RemovePaths = () => {
85
- const Routes = [];
86
-
87
- _HTTPAdmin._router.stack.forEach((R) => {
88
- if (R.route === undefined) {
89
- Routes.push(R);
90
- return;
91
- }
92
-
93
- if (R.route.path.startsWith(`/zwave-js/${_NetworkID}/smartstart-event`)) {
94
- return;
95
- }
96
-
97
- if (R.route.path.startsWith(`/zwave-js/smartstart-scanner`)) {
98
- return;
99
- }
100
-
101
- Routes.push(R);
102
- });
103
-
104
- return Routes;
105
- };
106
-
107
84
  const Stop = () => {
108
85
  if (_NetworkID !== undefined) {
109
- _HTTPAdmin._router.stack = RemovePaths();
86
+ const Check = (Route) => {
87
+ if (Route.route === undefined) {
88
+ return true;
89
+ }
90
+ if (
91
+ !Route.route.path.startsWith(`/zwave-js/smartstart-scanner`) &&
92
+ !Route.route.path.startsWith(`/zwave-js/${_NetworkID}/smartstart-event`)
93
+ ) {
94
+ return true;
95
+ }
96
+ return false;
97
+ };
98
+
99
+ _HTTPAdmin._router.stack = _HTTPAdmin._router.stack.filter(Check);
110
100
 
111
101
  _Enabled = false;
112
102
  _Callback = undefined;
@@ -950,11 +950,13 @@
950
950
  $('#node-input-serialPort').val('Select Port');
951
951
  }
952
952
 
953
+ const OV = this.serialPort ?? '';
954
+
953
955
  $('#node-input-serialPort').change(function (event) {
954
956
  var Value = event.target.value;
955
957
 
956
958
  if (Value === 'Custom Path...') {
957
- Value = prompt('Enter custom serial path.');
959
+ Value = prompt('Enter custom serial path.', OV);
958
960
  if (Value !== null) {
959
961
  Value = Value.trim();
960
962
  $('#node-input-serialPort').append(new Option(Value, Value));
@@ -914,7 +914,9 @@ module.exports = function (RED) {
914
914
  property: 'isLow'
915
915
  })
916
916
  },
917
- statistics: N.statistics
917
+ statistics: N.isControllerNode
918
+ ? Driver.controller.statistics
919
+ : N.statistics
918
920
  });
919
921
  });
920
922
  Nodes.sort((A, B) => A.nodeId - B.nodeId);