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 +10 -4
- package/README.md +4 -4
- package/package.json +3 -3
- package/resources/MeshMap/Map.html +26 -11
- package/resources/MeshMap/maprender.js +78 -29
- package/resources/UITab/client.js +238 -196
- package/zwave-js/ui/server.js +12 -11
- package/zwave-js/ui/smartstart/server.js +14 -24
- package/zwave-js/zwave-js.html +3 -1
- package/zwave-js/zwave-js.js +3 -1
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
|
-
-
|
|
17
|
-
- An entierly new
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|

|
|
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
|
-
- `
|
|
51
|
-
- `
|
|
52
|
-
- `
|
|
53
|
-
- `
|
|
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.
|
|
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.
|
|
12
|
+
"zwave-js": "^9.2.1"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"eslint": "^8.
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
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
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
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
|
-
|
|
2281
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
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
|
|
package/zwave-js/ui/server.js
CHANGED
|
@@ -265,7 +265,7 @@ class UIServer {
|
|
|
265
265
|
const timeout = setTimeout(() => {
|
|
266
266
|
TimedOut = true;
|
|
267
267
|
res.status(504).end();
|
|
268
|
-
},
|
|
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
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
Routes.push(R);
|
|
464
|
-
return;
|
|
460
|
+
const Check = (Route) => {
|
|
461
|
+
if (Route.route === undefined) {
|
|
462
|
+
return true;
|
|
465
463
|
}
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
|
|
464
|
+
if (
|
|
465
|
+
!Route.route.path.startsWith(`/zwave-js/${this._NetworkIdentifier}`)
|
|
466
|
+
) {
|
|
467
|
+
return true;
|
|
469
468
|
}
|
|
470
|
-
});
|
|
471
469
|
|
|
472
|
-
|
|
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
|
-
|
|
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;
|
package/zwave-js/zwave-js.html
CHANGED
|
@@ -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));
|
package/zwave-js/zwave-js.js
CHANGED
|
@@ -914,7 +914,9 @@ module.exports = function (RED) {
|
|
|
914
914
|
property: 'isLow'
|
|
915
915
|
})
|
|
916
916
|
},
|
|
917
|
-
statistics: N.
|
|
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);
|