@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/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
- newReadParent.children[0].children.push(slotProps.node);
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
- this.readDevices[foundDeviceIndex].children[0].children.push(slotProps.node);
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
- slotProps.node.label = pointDisplayName;
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
- this.addToReadDevices(this.pointsToRead);
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
+ &nbsp; {{slotProps.node.children ? slotProps.node.children.length : 0}} &nbsp;
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
- let readConfig = new ReadCommandConfig(node.pointsToRead, node.object_props, node.roundDecimal);
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" :filters="filters"
137
- v-model:filters="filters" :sortMode="'multiple'" @filter="onFilter" scrollable scrollHeight="800px"
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></p-column>
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: false,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitpoolos/edge-bacnet",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "A bacnet gateway for node-red",
5
5
  "dependencies": {
6
6
  "@plus4nodered/ts-node-bacnet": "^1.0.0-beta.2",
@@ -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) * 100 || 0),
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
- min-height: 800px !important; /* Force table to maintain minimum height */
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
  }