@bitpoolos/edge-bacnet 1.6.1 → 1.6.3
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 +49 -0
- package/bacnet_client.js +87 -56
- package/bacnet_gateway.html +240 -18
- package/bacnet_gateway.js +46 -11
- package/bacnet_inspector.js +88 -12
- package/bacnet_inspector_worker.js +15 -5
- package/bacnet_read.html +229 -8
- package/bacnet_read.js +13 -1
- package/common.js +7 -5
- package/inspector.html +60 -13
- package/package.json +1 -1
- package/resources/downloadAsHtml.js +8 -8
- package/resources/inspectorStyles.css +33 -21
- package/resources/style.css +28 -1
- package/ssrHtmlExporter.js +8 -8
- package/treeBuilder.js +80 -5
package/bacnet_read.html
CHANGED
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
progressBarValue: ref(),
|
|
63
63
|
rightClickedDevice: ref(),
|
|
64
64
|
rightClickedPoint: ref(),
|
|
65
|
+
rightClickedMstpFolder: ref(),
|
|
65
66
|
showDeviceNameDialog: ref(false),
|
|
66
67
|
showPointNameDialog: ref(false),
|
|
67
68
|
deviceDisplayNameValue: ref(),
|
|
@@ -288,7 +289,13 @@
|
|
|
288
289
|
newReadParent = JSON.parse(JSON.stringify(parentDevice));
|
|
289
290
|
}
|
|
290
291
|
newReadParent.children[0].children = [];
|
|
291
|
-
|
|
292
|
+
|
|
293
|
+
// Create a copy of the point with preserved display name
|
|
294
|
+
let pointCopy = JSON.parse(JSON.stringify(slotProps.node));
|
|
295
|
+
// Ensure display name is preserved from the original point
|
|
296
|
+
pointCopy.label = slotProps.node.label;
|
|
297
|
+
newReadParent.children[0].children.push(pointCopy);
|
|
298
|
+
|
|
292
299
|
while (newReadParent.children.length > 1) {
|
|
293
300
|
newReadParent.children.forEach(function (child, index) {
|
|
294
301
|
if (child.label.includes("MSTP")) {
|
|
@@ -307,7 +314,10 @@
|
|
|
307
314
|
(ele) => ele.pointName == slotProps.node.pointName
|
|
308
315
|
);
|
|
309
316
|
if (pointIndex == -1) {
|
|
310
|
-
|
|
317
|
+
// Create a copy of the point with preserved display name
|
|
318
|
+
let pointCopy = JSON.parse(JSON.stringify(slotProps.node));
|
|
319
|
+
pointCopy.label = slotProps.node.label;
|
|
320
|
+
this.readDevices[foundDeviceIndex].children[0].children.push(pointCopy);
|
|
311
321
|
}
|
|
312
322
|
}
|
|
313
323
|
|
|
@@ -321,6 +331,10 @@
|
|
|
321
331
|
}
|
|
322
332
|
|
|
323
333
|
let point = this.pointList[key][slotProps.node.pointName];
|
|
334
|
+
// Preserve any existing display name when adding to pointsToRead
|
|
335
|
+
if (slotProps.node.label !== slotProps.node.pointName) {
|
|
336
|
+
point.displayName = slotProps.node.label;
|
|
337
|
+
}
|
|
324
338
|
this.pointsToRead[key][point.objectName] = point;
|
|
325
339
|
|
|
326
340
|
//force a deploy state
|
|
@@ -566,6 +580,27 @@
|
|
|
566
580
|
pointMenu.classList.remove("pointAddedToRead");
|
|
567
581
|
}
|
|
568
582
|
},
|
|
583
|
+
// NEW: Handle right-click on MSTP network folders
|
|
584
|
+
onMstpFolderRightClick(slotProps, event) {
|
|
585
|
+
let app = this;
|
|
586
|
+
app.rightClickedMstpFolder = slotProps;
|
|
587
|
+
event.preventDefault();
|
|
588
|
+
event.stopPropagation();
|
|
589
|
+
|
|
590
|
+
// Hide other context menus first
|
|
591
|
+
const menu = document.querySelector(".context-menu");
|
|
592
|
+
const pointMenu = document.querySelector(".point-context-menu");
|
|
593
|
+
if (menu) menu.style.display = "none";
|
|
594
|
+
if (pointMenu) pointMenu.style.display = "none";
|
|
595
|
+
|
|
596
|
+
const mstpFolderMenu = document.querySelector(".mstp-folder-context-menu");
|
|
597
|
+
|
|
598
|
+
if (mstpFolderMenu) {
|
|
599
|
+
mstpFolderMenu.style.setProperty("--mouse-x", event.clientX + "px");
|
|
600
|
+
mstpFolderMenu.style.setProperty("--mouse-y", event.clientY + "px");
|
|
601
|
+
mstpFolderMenu.style.display = "block";
|
|
602
|
+
}
|
|
603
|
+
},
|
|
569
604
|
handleContextMenuClick(type) {
|
|
570
605
|
let app = this;
|
|
571
606
|
switch (type) {
|
|
@@ -589,6 +624,17 @@
|
|
|
589
624
|
break;
|
|
590
625
|
}
|
|
591
626
|
},
|
|
627
|
+
// NEW: Handle context menu clicks for MSTP folders
|
|
628
|
+
handleMstpFolderContextMenuClick(type) {
|
|
629
|
+
let app = this;
|
|
630
|
+
switch (type) {
|
|
631
|
+
case "updateAllDevices":
|
|
632
|
+
app.updateAllMstpDevices(app.rightClickedMstpFolder);
|
|
633
|
+
break;
|
|
634
|
+
default:
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
},
|
|
592
638
|
handlePointContextMenuClick(type) {
|
|
593
639
|
let app = this;
|
|
594
640
|
switch (type) {
|
|
@@ -603,18 +649,52 @@
|
|
|
603
649
|
break;
|
|
604
650
|
}
|
|
605
651
|
},
|
|
652
|
+
// NEW: Update all devices within an MSTP network folder
|
|
653
|
+
updateAllMstpDevices(slotProps) {
|
|
654
|
+
let app = this;
|
|
655
|
+
|
|
656
|
+
if (!slotProps || !slotProps.node || !slotProps.node.children) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const mstpDevices = slotProps.node.children;
|
|
661
|
+
let updatedCount = 0;
|
|
662
|
+
|
|
663
|
+
// Iterate through all MSTP devices in the folder
|
|
664
|
+
mstpDevices.forEach(function (mstpDevice) {
|
|
665
|
+
// Find the device in the device list
|
|
666
|
+
let device = app.getDeviceFromDeviceList(mstpDevice.ipAddr, mstpDevice.deviceId);
|
|
667
|
+
if (device) {
|
|
668
|
+
app.nodeService.updatePointsForDevice(device).then(function (result) {
|
|
669
|
+
updatedCount++;
|
|
670
|
+
}).catch(function (error) {
|
|
671
|
+
// Handle error silently
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// Show user feedback
|
|
677
|
+
if (mstpDevices.length > 0) {
|
|
678
|
+
app.$toast?.add({
|
|
679
|
+
severity: 'info',
|
|
680
|
+
summary: 'Update Started',
|
|
681
|
+
detail: `Updating points for ${mstpDevices.length} devices in ${slotProps.node.label}`,
|
|
682
|
+
life: 3000
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
},
|
|
606
686
|
purgeDevice(slotProps) {
|
|
607
687
|
let app = this;
|
|
608
688
|
let device = app.getDeviceFromDeviceList(slotProps.node.ipAddr, slotProps.node.deviceId);
|
|
609
689
|
if (device) {
|
|
610
|
-
app.nodeService.purgeDevice(device).then(function (result) {});
|
|
690
|
+
app.nodeService.purgeDevice(device).then(function (result) { });
|
|
611
691
|
}
|
|
612
692
|
},
|
|
613
693
|
updatePointsForDevice(slotProps) {
|
|
614
694
|
let app = this;
|
|
615
695
|
let device = app.getDeviceFromDeviceList(slotProps.node.ipAddr, slotProps.node.deviceId);
|
|
616
696
|
if (device) {
|
|
617
|
-
app.nodeService.updatePointsForDevice(device).then(function (result) {});
|
|
697
|
+
app.nodeService.updatePointsForDevice(device).then(function (result) { });
|
|
618
698
|
}
|
|
619
699
|
},
|
|
620
700
|
updatePoint(slotProps) {
|
|
@@ -625,7 +705,7 @@
|
|
|
625
705
|
});
|
|
626
706
|
const deviceKey = `${app.getDeviceAddress(device.address)}-${device.deviceId}`;
|
|
627
707
|
if (device) {
|
|
628
|
-
app.nodeService.updatePoint(deviceKey, pointKey).then(function (result) {});
|
|
708
|
+
app.nodeService.updatePoint(deviceKey, pointKey).then(function (result) { });
|
|
629
709
|
}
|
|
630
710
|
},
|
|
631
711
|
setDeviceName() {
|
|
@@ -660,13 +740,95 @@
|
|
|
660
740
|
|
|
661
741
|
app.nodeService.setPointDisplayName(deviceKey, pointName, pointDisplayName).then(function (result) {
|
|
662
742
|
if (result) {
|
|
663
|
-
|
|
743
|
+
// Update the display name across all UI representations
|
|
744
|
+
app.syncPointDisplayNameAcrossUI(deviceKey, pointName, pointDisplayName);
|
|
745
|
+
app.updatePointsToReadDisplayName(deviceKey, pointName, pointDisplayName);
|
|
664
746
|
}
|
|
665
747
|
});
|
|
666
748
|
}
|
|
667
749
|
|
|
668
750
|
app.showPointNameDialog = false;
|
|
669
751
|
},
|
|
752
|
+
// NEW: Centralized method to sync display names across all UI trees
|
|
753
|
+
syncPointDisplayNameAcrossUI(deviceKey, pointName, newDisplayName) {
|
|
754
|
+
let app = this;
|
|
755
|
+
|
|
756
|
+
// Update in main devices tree
|
|
757
|
+
app.updatePointDisplayNameInDevicesTree(deviceKey, pointName, newDisplayName);
|
|
758
|
+
|
|
759
|
+
// Update in read devices tree
|
|
760
|
+
app.updatePointDisplayNameInReadDevicesTree(deviceKey, pointName, newDisplayName);
|
|
761
|
+
|
|
762
|
+
// Force UI update
|
|
763
|
+
app.$forceUpdate();
|
|
764
|
+
},
|
|
765
|
+
// NEW: Update display name in main devices tree
|
|
766
|
+
updatePointDisplayNameInDevicesTree(deviceKey, pointName, newDisplayName) {
|
|
767
|
+
let app = this;
|
|
768
|
+
let [ipAddress, deviceId] = deviceKey.split('-');
|
|
769
|
+
|
|
770
|
+
// Find the device in main tree
|
|
771
|
+
let deviceIndex = app.devices ? app.devices.findIndex(device =>
|
|
772
|
+
device.ipAddr === ipAddress && device.deviceId.toString() === deviceId) : -1;
|
|
773
|
+
|
|
774
|
+
if (deviceIndex !== -1) {
|
|
775
|
+
// Update direct device points
|
|
776
|
+
let pointIndex = app.devices[deviceIndex].children[0].children.findIndex(point =>
|
|
777
|
+
point.pointName === pointName);
|
|
778
|
+
if (pointIndex !== -1) {
|
|
779
|
+
app.devices[deviceIndex].children[0].children[pointIndex].label = newDisplayName;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Update MSTP device points if applicable
|
|
783
|
+
app.devices[deviceIndex].children.forEach(child => {
|
|
784
|
+
if (child.label && child.label.includes("MSTP") && child.children) {
|
|
785
|
+
child.children.forEach(mstpDevice => {
|
|
786
|
+
if (mstpDevice.deviceId.toString() === deviceId) {
|
|
787
|
+
let mstpPointIndex = mstpDevice.children[0].children.findIndex(point =>
|
|
788
|
+
point.pointName === pointName);
|
|
789
|
+
if (mstpPointIndex !== -1) {
|
|
790
|
+
mstpDevice.children[0].children[mstpPointIndex].label = newDisplayName;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
// NEW: Update display name in read devices tree
|
|
799
|
+
updatePointDisplayNameInReadDevicesTree(deviceKey, pointName, newDisplayName) {
|
|
800
|
+
let app = this;
|
|
801
|
+
let [ipAddress, deviceId] = deviceKey.split('-');
|
|
802
|
+
|
|
803
|
+
if (!app.readDevices) return;
|
|
804
|
+
|
|
805
|
+
// Find the device in read devices tree
|
|
806
|
+
let readDeviceIndex = app.readDevices.findIndex(device =>
|
|
807
|
+
device.deviceId.toString() === deviceId);
|
|
808
|
+
|
|
809
|
+
if (readDeviceIndex !== -1) {
|
|
810
|
+
let pointIndex = app.readDevices[readDeviceIndex].children[0].children.findIndex(point =>
|
|
811
|
+
point.pointName === pointName);
|
|
812
|
+
if (pointIndex !== -1) {
|
|
813
|
+
app.readDevices[readDeviceIndex].children[0].children[pointIndex].label = newDisplayName;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
// NEW: Update display name in pointsToRead data structure
|
|
818
|
+
updatePointsToReadDisplayName(deviceKey, pointName, newDisplayName) {
|
|
819
|
+
let app = this;
|
|
820
|
+
|
|
821
|
+
if (app.pointsToRead && app.pointsToRead[deviceKey]) {
|
|
822
|
+
// Find the point by objectName (since pointsToRead uses objectName as key)
|
|
823
|
+
for (let objectName in app.pointsToRead[deviceKey]) {
|
|
824
|
+
let point = app.pointsToRead[deviceKey][objectName];
|
|
825
|
+
if (point && point.objectName === pointName) {
|
|
826
|
+
point.displayName = newDisplayName;
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
},
|
|
670
832
|
getDeviceFromDeviceList(ip, id) {
|
|
671
833
|
let app = this;
|
|
672
834
|
let device = app.deviceList.find((ele) => {
|
|
@@ -896,7 +1058,30 @@
|
|
|
896
1058
|
app.$forceUpdate();
|
|
897
1059
|
},
|
|
898
1060
|
refreshReadListTree() {
|
|
899
|
-
|
|
1061
|
+
// Enhanced refresh that maintains display names
|
|
1062
|
+
let app = this;
|
|
1063
|
+
|
|
1064
|
+
// First apply any saved display names to the current pointsToRead
|
|
1065
|
+
app.applyStoredDisplayNames();
|
|
1066
|
+
|
|
1067
|
+
// Then rebuild the read list tree
|
|
1068
|
+
app.addToReadDevices(app.pointsToRead);
|
|
1069
|
+
},
|
|
1070
|
+
// NEW: Apply stored display names from backend to current data
|
|
1071
|
+
applyStoredDisplayNames() {
|
|
1072
|
+
let app = this;
|
|
1073
|
+
|
|
1074
|
+
if (!app.pointsToRead) return;
|
|
1075
|
+
|
|
1076
|
+
// Send current pointsToRead to backend to apply any stored display names
|
|
1077
|
+
let msg = { applyDisplayNames: true };
|
|
1078
|
+
// This will be handled by the backend node to apply stored display names
|
|
1079
|
+
app.nodeService.applyDisplayNames(app.pointsToRead).then(function (result) {
|
|
1080
|
+
if (result) {
|
|
1081
|
+
// Refresh the main tree with updated display names
|
|
1082
|
+
app.getData();
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
900
1085
|
},
|
|
901
1086
|
calculateMstpCount(slotProps) {
|
|
902
1087
|
let count = 0;
|
|
@@ -912,6 +1097,7 @@
|
|
|
912
1097
|
let count = 0;
|
|
913
1098
|
return count;
|
|
914
1099
|
},
|
|
1100
|
+
|
|
915
1101
|
},
|
|
916
1102
|
components: {
|
|
917
1103
|
"p-tree": primevue.tree,
|
|
@@ -970,6 +1156,13 @@
|
|
|
970
1156
|
pointMenu.style.display = "none";
|
|
971
1157
|
});
|
|
972
1158
|
|
|
1159
|
+
var mstpFolderMenu = document.querySelector(".mstp-folder-context-menu");
|
|
1160
|
+
window.addEventListener("click", (event) => {
|
|
1161
|
+
if (menu) menu.style.display = "none";
|
|
1162
|
+
if (pointMenu) pointMenu.style.display = "none";
|
|
1163
|
+
if (mstpFolderMenu) mstpFolderMenu.style.display = "none";
|
|
1164
|
+
});
|
|
1165
|
+
|
|
973
1166
|
function handleCheckboxClick() {
|
|
974
1167
|
if (this.id == "node-input-object_property_simplePayload") {
|
|
975
1168
|
document.getElementById("node-input-object_property_fullObject").checked = false;
|
|
@@ -1113,6 +1306,18 @@
|
|
|
1113
1306
|
</ul>
|
|
1114
1307
|
<!-- End Point Context Menu -->
|
|
1115
1308
|
|
|
1309
|
+
<!-- Start MSTP Folder Context Menu -->
|
|
1310
|
+
<ul
|
|
1311
|
+
class="red-ui-menu red-ui-menu-dropdown red-ui-menu-dropdown-direction-right red-ui-menu-dropdown-noicons red-ui-menu-dropdown-submenus mstp-folder-context-menu">
|
|
1312
|
+
<li class="context-menu-item" @click="handleMstpFolderContextMenuClick('updateAllDevices')">
|
|
1313
|
+
<a class="red-ui-menu-label">
|
|
1314
|
+
<i class="pi pi-refresh context-menu-icon"></i>
|
|
1315
|
+
<span class="context-menu-item-text">Update All Devices</span>
|
|
1316
|
+
</a>
|
|
1317
|
+
</li>
|
|
1318
|
+
</ul>
|
|
1319
|
+
<!-- End MSTP Folder Context Menu -->
|
|
1320
|
+
|
|
1116
1321
|
<!--
|
|
1117
1322
|
*
|
|
1118
1323
|
*
|
|
@@ -1194,6 +1399,7 @@
|
|
|
1194
1399
|
filterMode="lenient"
|
|
1195
1400
|
filterPlaceholder="No results found."
|
|
1196
1401
|
v-if="hasData()">
|
|
1402
|
+
|
|
1197
1403
|
<template #device="slotProps">
|
|
1198
1404
|
<div @contextmenu="onDeviceRightClick(slotProps, $event)" class="p-treenode-label">
|
|
1199
1405
|
<div v-if="isDeviceActive(slotProps)" class="deviceLabelParent">
|
|
@@ -1233,6 +1439,20 @@
|
|
|
1233
1439
|
</div>
|
|
1234
1440
|
</template>
|
|
1235
1441
|
|
|
1442
|
+
|
|
1443
|
+
<template #mstpfolder="slotProps">
|
|
1444
|
+
<div @contextmenu="onMstpFolderRightClick(slotProps, $event)" class="p-treenode-label">
|
|
1445
|
+
<div class="deviceLabelParent">
|
|
1446
|
+
<b class="mstpLabel">
|
|
1447
|
+
<span>{{slotProps.node.label}}</span>
|
|
1448
|
+
<span class="mstpDeviceCount" >
|
|
1449
|
+
{{slotProps.node.children ? slotProps.node.children.length : 0}}
|
|
1450
|
+
</span>
|
|
1451
|
+
</b>
|
|
1452
|
+
</div>
|
|
1453
|
+
</div>
|
|
1454
|
+
</template>
|
|
1455
|
+
|
|
1236
1456
|
<template #point="slotProps" v-model:class="pointContent">
|
|
1237
1457
|
<div @contextmenu="onPointRightClick(slotProps, $event)">
|
|
1238
1458
|
<b class="pointLabel">{{slotProps.node.label}}</b>
|
|
@@ -1253,6 +1473,7 @@
|
|
|
1253
1473
|
</button>
|
|
1254
1474
|
</div>
|
|
1255
1475
|
</template>
|
|
1476
|
+
|
|
1256
1477
|
</p-tree>
|
|
1257
1478
|
<div v-else style="text-align: center; padding-top: 20px;">
|
|
1258
1479
|
<a>Building Tree...</a>
|
|
@@ -1492,4 +1713,4 @@
|
|
|
1492
1713
|
<li><a href="https://wiki.bitpool.com/">wiki.bitpool.com</a> - find more documentation.</li>
|
|
1493
1714
|
<li><a href="https://bacnet.org/">BACnet</a> - find more about the protocol.</li>
|
|
1494
1715
|
</ul>
|
|
1495
|
-
</script>
|
|
1716
|
+
</script>
|
package/bacnet_read.js
CHANGED
|
@@ -99,7 +99,19 @@ module.exports = function (RED) {
|
|
|
99
99
|
useDeviceName = node.useDeviceName;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
// Ensure pointsToRead has the latest display names before processing
|
|
103
|
+
let pointsToReadWithDisplayNames = JSON.parse(JSON.stringify(node.pointsToRead));
|
|
104
|
+
|
|
105
|
+
// Apply any stored display names from the backend data model
|
|
106
|
+
for (let deviceKey in pointsToReadWithDisplayNames) {
|
|
107
|
+
for (let pointKey in pointsToReadWithDisplayNames[deviceKey]) {
|
|
108
|
+
let point = pointsToReadWithDisplayNames[deviceKey][pointKey];
|
|
109
|
+
// The display name should already be preserved in the point object
|
|
110
|
+
// from the setPointDisplayName operations and addPointClicked enhancements
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let readConfig = new ReadCommandConfig(pointsToReadWithDisplayNames, node.object_props, node.roundDecimal);
|
|
103
115
|
|
|
104
116
|
let output = {
|
|
105
117
|
type: "Read",
|
package/common.js
CHANGED
|
@@ -65,7 +65,8 @@ class BacnetClientConfig {
|
|
|
65
65
|
retries,
|
|
66
66
|
cacheFileEnabled,
|
|
67
67
|
sanitise_device_schedule,
|
|
68
|
-
portRangeMatrix
|
|
68
|
+
portRangeMatrix,
|
|
69
|
+
enable_device_discovery
|
|
69
70
|
) {
|
|
70
71
|
this.apduTimeout = apduTimeout;
|
|
71
72
|
this.localIpAdrress = localIpAdrress;
|
|
@@ -84,6 +85,7 @@ class BacnetClientConfig {
|
|
|
84
85
|
this.cacheFileEnabled = cacheFileEnabled;
|
|
85
86
|
this.sanitise_device_schedule = sanitise_device_schedule;
|
|
86
87
|
this.portRangeMatrix = this.generatePortRangeArray(portRangeMatrix);
|
|
88
|
+
this.enable_device_discovery = enable_device_discovery;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
generatePortRangeArray(rangeMatrix) {
|
|
@@ -255,7 +257,7 @@ async function Store_Config(data) {
|
|
|
255
257
|
JSON.parse(tempContent);
|
|
256
258
|
} catch (verifyError) {
|
|
257
259
|
console.error("Temporary file validation failed:", verifyError);
|
|
258
|
-
await fs2.unlink(tempFile).catch(() => {});
|
|
260
|
+
await fs2.unlink(tempFile).catch(() => { });
|
|
259
261
|
return false;
|
|
260
262
|
}
|
|
261
263
|
|
|
@@ -275,8 +277,8 @@ async function Store_Config(data) {
|
|
|
275
277
|
|
|
276
278
|
// Cleanup temporary file if it exists
|
|
277
279
|
try {
|
|
278
|
-
await fs2.unlink(tempFile).catch(() => {});
|
|
279
|
-
} catch (cleanupError) {}
|
|
280
|
+
await fs2.unlink(tempFile).catch(() => { });
|
|
281
|
+
} catch (cleanupError) { }
|
|
280
282
|
|
|
281
283
|
// If main file is corrupted and backup exists, restore from backup
|
|
282
284
|
try {
|
|
@@ -396,7 +398,7 @@ async function Store_Config_Server(data) {
|
|
|
396
398
|
//console.log("Store_Config_Server writeFile error: ", err);
|
|
397
399
|
}
|
|
398
400
|
});
|
|
399
|
-
} catch (err) {}
|
|
401
|
+
} catch (err) { }
|
|
400
402
|
}
|
|
401
403
|
|
|
402
404
|
// READ CONFIG SYNC FUNCTION - BACNET SERVER ======================================
|
package/inspector.html
CHANGED
|
@@ -133,9 +133,9 @@
|
|
|
133
133
|
<div class="center">
|
|
134
134
|
<p class="headertext">{{siteName}} - Point Status Display</p>
|
|
135
135
|
</div>
|
|
136
|
-
<p-datatable :value="displayData" paginator :rows="12" filterDisplay="menu"
|
|
137
|
-
v-model:filters="filters" :sortMode="'multiple'" @filter="onFilter"
|
|
138
|
-
class="datatable fixed-height-table">
|
|
136
|
+
<p-datatable :value="displayData" paginator scrollable scrollHeight="800px" :rows="12" filterDisplay="menu"
|
|
137
|
+
:filters="filters" v-model:filters="filters" :sortMode="'multiple'" @filter="onFilter"
|
|
138
|
+
class="datatable fixed-height-table" :loading="loading">
|
|
139
139
|
<template #header>
|
|
140
140
|
<div class="tableHeaderDiv">
|
|
141
141
|
<div style="margin-right: 5px">
|
|
@@ -177,8 +177,20 @@
|
|
|
177
177
|
</div>
|
|
178
178
|
</div>
|
|
179
179
|
</template>
|
|
180
|
+
<template #loading>
|
|
181
|
+
<div class="loading-overlay">
|
|
182
|
+
<div class="loading-spinner">
|
|
183
|
+
<i class="pi pi-spin pi-spinner" style="font-size: 2rem"></i>
|
|
184
|
+
<p>Loading data...</p>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</template>
|
|
180
188
|
<p-column v-for="col in visibleColumns" :key="col.field" :field="col.field" :header="col.header" sortable
|
|
181
|
-
filter
|
|
189
|
+
filter>
|
|
190
|
+
<template #body="slotProps" v-if="col.field === 'objectType'">
|
|
191
|
+
{{ getObjectTypeString(slotProps.data.objectType) }}
|
|
192
|
+
</template>
|
|
193
|
+
</p-column>
|
|
182
194
|
<template #paginatorstart>
|
|
183
195
|
|
|
184
196
|
</template>
|
|
@@ -188,6 +200,7 @@
|
|
|
188
200
|
</span>
|
|
189
201
|
</template>
|
|
190
202
|
</p-datatable>
|
|
203
|
+
|
|
191
204
|
</div>
|
|
192
205
|
</div>
|
|
193
206
|
</div>
|
|
@@ -199,7 +212,7 @@
|
|
|
199
212
|
data() {
|
|
200
213
|
return {
|
|
201
214
|
tableData: null,
|
|
202
|
-
loading:
|
|
215
|
+
loading: true,
|
|
203
216
|
totalRecords: 0,
|
|
204
217
|
first: 0,
|
|
205
218
|
lazyParams: {},
|
|
@@ -263,6 +276,7 @@
|
|
|
263
276
|
this.observeHeaderHeight();
|
|
264
277
|
|
|
265
278
|
this.selectedColumns = JSON.parse(JSON.stringify(this.allColumns));
|
|
279
|
+
this.loading = true;
|
|
266
280
|
|
|
267
281
|
fetch("/getModelStats")
|
|
268
282
|
.then((response) => {
|
|
@@ -288,19 +302,22 @@
|
|
|
288
302
|
// Calculate percentages
|
|
289
303
|
const total = tableData.length || 1; // Avoid division by zero
|
|
290
304
|
app.statPercentages = {
|
|
291
|
-
readCount: Math.round((data.statCounts.readCount / total) * 100
|
|
292
|
-
ok: Math.round((data.statCounts.statBlock.ok / total) * 100
|
|
293
|
-
error: Math.round((data.statCounts.statBlock.error / total) * 100
|
|
294
|
-
missing: Math.round((data.statCounts.statBlock.missing / total) * 100
|
|
295
|
-
warnings: Math.round((data.statCounts.statBlock.warnings / total) * 100
|
|
296
|
-
deviceIdChange: Math.round((data.statCounts.statBlock.deviceIdChange / total) * 100
|
|
297
|
-
deviceIdConflict: Math.round((data.statCounts.statBlock.deviceIdConflict / total) * 100
|
|
298
|
-
unmapped: Math.round((data.statCounts.statBlock.unmapped / total) * 100
|
|
305
|
+
readCount: Math.round((data.statCounts.readCount / total) * 10000) / 100,
|
|
306
|
+
ok: Math.round((data.statCounts.statBlock.ok / total) * 10000) / 100,
|
|
307
|
+
error: Math.round((data.statCounts.statBlock.error / total) * 10000) / 100,
|
|
308
|
+
missing: Math.round((data.statCounts.statBlock.missing / total) * 10000) / 100,
|
|
309
|
+
warnings: Math.round((data.statCounts.statBlock.warnings / total) * 10000) / 100,
|
|
310
|
+
deviceIdChange: Math.round((data.statCounts.statBlock.deviceIdChange / total) * 10000) / 100,
|
|
311
|
+
deviceIdConflict: Math.round((data.statCounts.statBlock.deviceIdConflict / total) * 10000) / 100,
|
|
312
|
+
unmapped: Math.round((data.statCounts.statBlock.unmapped / total) * 10000) / 100
|
|
299
313
|
};
|
|
300
314
|
})
|
|
301
315
|
.catch((error) => {
|
|
302
316
|
// Handle any errors
|
|
303
317
|
console.error("Error:", error);
|
|
318
|
+
})
|
|
319
|
+
.finally(() => {
|
|
320
|
+
app.loading = false;
|
|
304
321
|
});
|
|
305
322
|
},
|
|
306
323
|
computed: {
|
|
@@ -318,6 +335,36 @@
|
|
|
318
335
|
},
|
|
319
336
|
},
|
|
320
337
|
methods: {
|
|
338
|
+
getObjectTypeString(objectType) {
|
|
339
|
+
switch (objectType) {
|
|
340
|
+
case 0:
|
|
341
|
+
return "Analog Input";
|
|
342
|
+
case 1:
|
|
343
|
+
return "Analog Output";
|
|
344
|
+
case 2:
|
|
345
|
+
return "Analog Value";
|
|
346
|
+
case 3:
|
|
347
|
+
return "Binary Input";
|
|
348
|
+
case 4:
|
|
349
|
+
return "Binary Output";
|
|
350
|
+
case 5:
|
|
351
|
+
return "Binary Value";
|
|
352
|
+
case 8:
|
|
353
|
+
return "Device";
|
|
354
|
+
case 10:
|
|
355
|
+
return "File";
|
|
356
|
+
case 13:
|
|
357
|
+
return "Multistate Input";
|
|
358
|
+
case 14:
|
|
359
|
+
return "Multistate Output";
|
|
360
|
+
case 19:
|
|
361
|
+
return "Multistate Value";
|
|
362
|
+
case 40:
|
|
363
|
+
return "Character String";
|
|
364
|
+
default:
|
|
365
|
+
return "";
|
|
366
|
+
}
|
|
367
|
+
},
|
|
321
368
|
statusItemClicked(e) {
|
|
322
369
|
// Get the filter category from the clicked item's text content
|
|
323
370
|
const clickedItem = e.currentTarget;
|
package/package.json
CHANGED
|
@@ -146,14 +146,14 @@ async function processPrimeVueHtml(appData) {
|
|
|
146
146
|
// Calculate percentages
|
|
147
147
|
const total = this.tableData.length || 1; // Avoid division by zero
|
|
148
148
|
this.statPercentages = {
|
|
149
|
-
readCount: Math.round((this.statCounts.readCount / total) * 100
|
|
150
|
-
ok: Math.round((this.statCounts.statBlock.ok / total) * 100
|
|
151
|
-
error: Math.round((this.statCounts.statBlock.error / total) * 100
|
|
152
|
-
missing: Math.round((this.statCounts.statBlock.missing / total) * 100
|
|
153
|
-
warnings: Math.round((this.statCounts.statBlock.warnings / total) * 100
|
|
154
|
-
deviceIdChange: Math.round((this.statCounts.statBlock.deviceIdChange / total) * 100
|
|
155
|
-
deviceIdConflict: Math.round((this.statCounts.statBlock.deviceIdConflict / total) *
|
|
156
|
-
unmapped: Math.round((this.statCounts.statBlock.unmapped / total) * 100
|
|
149
|
+
readCount: Math.round((this.statCounts.readCount / total) * 10000) / 100,
|
|
150
|
+
ok: Math.round((this.statCounts.statBlock.ok / total) * 10000) / 100,
|
|
151
|
+
error: Math.round((this.statCounts.statBlock.error / total) * 10000) / 100,
|
|
152
|
+
missing: Math.round((this.statCounts.statBlock.missing / total) * 10000) / 100,
|
|
153
|
+
warnings: Math.round((this.statCounts.statBlock.warnings / total) * 10000) / 100,
|
|
154
|
+
deviceIdChange: Math.round((this.statCounts.statBlock.deviceIdChange / total) * 10000) / 100,
|
|
155
|
+
deviceIdConflict: Math.round((this.statCounts.statBlock.deviceIdConflict / total) * 10000) / 100,
|
|
156
|
+
unmapped: Math.round((this.statCounts.statBlock.unmapped / total) * 10000) / 100
|
|
157
157
|
};
|
|
158
158
|
},
|
|
159
159
|
computed: {
|
|
@@ -183,32 +183,15 @@ body {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
|
|
186
|
-
/* changes start */
|
|
187
|
-
|
|
188
|
-
/* Add these rules to forcefully control table height */
|
|
189
|
-
.fixed-height-table {
|
|
190
|
-
height: 800px !important; /* Fixed height matching scrollHeight */
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.fixed-height-table .p-datatable-wrapper {
|
|
194
|
-
height: calc(800px - 92px) !important; /* Subtract header and paginator height */
|
|
195
|
-
min-height: calc(800px - 92px) !important;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
186
|
.fixed-height-table .p-datatable-table {
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/* Make sure the table body expands to fill available space */
|
|
203
|
-
.fixed-height-table .p-datatable-tbody {
|
|
204
|
-
min-height: 760px !important; /* Approximate calculation for body only */
|
|
187
|
+
height: 70vh !important;
|
|
205
188
|
}
|
|
206
189
|
|
|
207
|
-
/* Add an empty row to maintain height when filtered results are few */
|
|
208
190
|
.p-datatable-emptymessage td {
|
|
209
191
|
height: 760px !important;
|
|
210
192
|
}
|
|
211
193
|
|
|
194
|
+
|
|
212
195
|
.row-missing {
|
|
213
196
|
background-color: #00adef;
|
|
214
197
|
}
|
|
@@ -234,7 +217,7 @@ body {
|
|
|
234
217
|
width: -webkit-fill-available;
|
|
235
218
|
margin: 5px;
|
|
236
219
|
left: 0px;
|
|
237
|
-
height: 1000px !important;
|
|
220
|
+
/* height: 1000px !important; */
|
|
238
221
|
}
|
|
239
222
|
|
|
240
223
|
.datatable-card {
|
|
@@ -242,7 +225,7 @@ body {
|
|
|
242
225
|
padding-bottom: 1rem !important;
|
|
243
226
|
padding-left: 2rem;
|
|
244
227
|
padding-right: 2rem;
|
|
245
|
-
height: 1000px !important;
|
|
228
|
+
/* height: 1000px !important; */
|
|
246
229
|
}
|
|
247
230
|
|
|
248
231
|
.p-multiselect-overlay {
|
|
@@ -500,4 +483,33 @@ body {
|
|
|
500
483
|
/* Ensure table takes minimum space when empty */
|
|
501
484
|
.p-datatable-empty-message {
|
|
502
485
|
height: 576px !important;
|
|
486
|
+
}
|
|
487
|
+
.loading-overlay {
|
|
488
|
+
position: absolute;
|
|
489
|
+
top: 0;
|
|
490
|
+
left: 0;
|
|
491
|
+
width: 100%;
|
|
492
|
+
height: 100%;
|
|
493
|
+
display: flex;
|
|
494
|
+
justify-content: center;
|
|
495
|
+
align-items: center;
|
|
496
|
+
background-color: rgba(255, 255, 255, 0.7);
|
|
497
|
+
z-index: 1000;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.loading-spinner {
|
|
501
|
+
display: flex;
|
|
502
|
+
flex-direction: column;
|
|
503
|
+
align-items: center;
|
|
504
|
+
gap: 1rem;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.loading-spinner p {
|
|
508
|
+
margin: 0;
|
|
509
|
+
color: #495057;
|
|
510
|
+
font-weight: 500;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.p-datatable .p-datatable-loading-overlay {
|
|
514
|
+
position: relative;
|
|
503
515
|
}
|