diff --git a/src/public/index.html b/src/public/index.html
index fb93514..7f43d9b 100644
--- a/src/public/index.html
+++ b/src/public/index.html
@@ -1379,6 +1379,31 @@
Colors the connection lines by the average SNR in the worst direction. Reload to update map.
+
+
+
+
+
+
+
+
+
Only show connections where data flows in both directions. Reload to update map.
+
+
+
+
+
+
Only show connections where at least one direction has SNR above this threshold. Leave empty to show all connections. Reload to update map.
+
+
+
+
+
+
+
+
If checked, all existing directions must meet the minimum SNR threshold (both directions if bidirectional, single direction if unidirectional).
+
+
@@ -1682,8 +1707,8 @@
function getConfigConnectionsTimePeriodInSeconds() {
const value = localStorage.getItem("config_connections_time_period_in_seconds");
- // default to 24 hours if unset
- return value != null ? parseInt(value) : 86400;
+ // default to 7 days if unset
+ return value != null ? parseInt(value) : 604800;
}
function setConfigConnectionsTimePeriodInSeconds(value) {
@@ -1703,6 +1728,51 @@
return localStorage.setItem("config_connections_colored_lines", value);
}
+ function getConfigConnectionsBidirectionalOnly() {
+ const value = localStorage.getItem("config_connections_bidirectional_only");
+ // disable bidirectional filter by default
+ if(value === null){
+ return false;
+ }
+ return value === "true";
+ }
+
+ function setConfigConnectionsBidirectionalOnly(value) {
+ return localStorage.setItem("config_connections_bidirectional_only", value);
+ }
+
+ function getConfigConnectionsMinSnrDb() {
+ const value = localStorage.getItem("config_connections_min_snr_db");
+ // default to null (unset)
+ if(value === null || value === ""){
+ return null;
+ }
+ const parsed = parseFloat(value);
+ return isNaN(parsed) ? null : parsed;
+ }
+
+ function setConfigConnectionsMinSnrDb(value) {
+ if(value === null || value === "" || value === undefined){
+ return localStorage.removeItem("config_connections_min_snr_db");
+ }
+ // Convert to string for localStorage (handles both number and string inputs)
+ const stringValue = typeof value === "number" ? value.toString() : String(value);
+ return localStorage.setItem("config_connections_min_snr_db", stringValue);
+ }
+
+ function getConfigConnectionsBidirectionalMinSnr() {
+ const value = localStorage.getItem("config_connections_bidirectional_min_snr");
+ // disable bidirectional minimum SNR by default
+ if(value === null){
+ return false;
+ }
+ return value === "true";
+ }
+
+ function setConfigConnectionsBidirectionalMinSnr(value) {
+ return localStorage.setItem("config_connections_bidirectional_min_snr", value);
+ }
+
function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
@@ -1726,6 +1796,9 @@
configTemperatureFormat: window.getConfigTemperatureFormat(),
configConnectionsTimePeriodInSeconds: window.getConfigConnectionsTimePeriodInSeconds(),
configConnectionsColoredLines: window.getConfigConnectionsColoredLines(),
+ configConnectionsBidirectionalOnly: window.getConfigConnectionsBidirectionalOnly(),
+ configConnectionsMinSnrDb: window.getConfigConnectionsMinSnrDb(),
+ configConnectionsBidirectionalMinSnr: window.getConfigConnectionsBidirectionalMinSnr(),
isShowingHardwareModels: false,
hardwareModelStats: null,
@@ -2643,6 +2716,15 @@
configConnectionsColoredLines() {
window.setConfigConnectionsColoredLines(this.configConnectionsColoredLines);
},
+ configConnectionsBidirectionalOnly() {
+ window.setConfigConnectionsBidirectionalOnly(this.configConnectionsBidirectionalOnly);
+ },
+ configConnectionsMinSnrDb() {
+ window.setConfigConnectionsMinSnrDb(this.configConnectionsMinSnrDb);
+ },
+ configConnectionsBidirectionalMinSnr() {
+ window.setConfigConnectionsBidirectionalMinSnr(this.configConnectionsBidirectionalMinSnr);
+ },
deviceMetricsTimeRange() {
this.loadNodeDeviceMetrics(this.selectedNode.node_id);
},
@@ -3214,6 +3296,48 @@
if (!otherNode || !otherNodeMarker) continue;
+ // Apply bidirectional filter
+ const configConnectionsBidirectionalOnly = getConfigConnectionsBidirectionalOnly();
+ if(configConnectionsBidirectionalOnly){
+ const hasDirectionAB = connection.direction_ab && connection.direction_ab.avg_snr_db != null;
+ const hasDirectionBA = connection.direction_ba && connection.direction_ba.avg_snr_db != null;
+ if(!hasDirectionAB || !hasDirectionBA){
+ continue;
+ }
+ }
+
+ // Apply minimum SNR filter
+ const configConnectionsMinSnrDb = getConfigConnectionsMinSnrDb();
+ if(configConnectionsMinSnrDb != null){
+ const snrAB = connection.direction_ab && connection.direction_ab.avg_snr_db != null ? connection.direction_ab.avg_snr_db : null;
+ const snrBA = connection.direction_ba && connection.direction_ba.avg_snr_db != null ? connection.direction_ba.avg_snr_db : null;
+
+ const configConnectionsBidirectionalMinSnr = getConfigConnectionsBidirectionalMinSnr();
+ let hasSnrAboveThreshold;
+
+ if(configConnectionsBidirectionalMinSnr){
+ // Bidirectional mode: ALL existing directions must meet threshold
+ const directionsToCheck = [];
+ if(snrAB != null) directionsToCheck.push(snrAB);
+ if(snrBA != null) directionsToCheck.push(snrBA);
+
+ if(directionsToCheck.length === 0){
+ // No SNR data in either direction, skip
+ hasSnrAboveThreshold = false;
+ } else {
+ // All existing directions must be above threshold
+ hasSnrAboveThreshold = directionsToCheck.every(snr => snr > configConnectionsMinSnrDb);
+ }
+ } else {
+ // Default mode: EITHER direction has SNR above threshold
+ hasSnrAboveThreshold = (snrAB != null && snrAB > configConnectionsMinSnrDb) || (snrBA != null && snrBA > configConnectionsMinSnrDb);
+ }
+
+ if(!hasSnrAboveThreshold){
+ continue;
+ }
+ }
+
// Calculate distance
const distanceInMeters = nodeMarker.getLatLng().distanceTo(otherNodeMarker.getLatLng()).toFixed(2);
const configConnectionsMaxDistanceInMeters = getConfigConnectionsMaxDistanceInMeters();
@@ -3611,6 +3735,48 @@
continue;
}
+ // Apply bidirectional filter
+ const configConnectionsBidirectionalOnly = getConfigConnectionsBidirectionalOnly();
+ if(configConnectionsBidirectionalOnly){
+ const hasDirectionAB = connection.direction_ab && connection.direction_ab.avg_snr_db != null;
+ const hasDirectionBA = connection.direction_ba && connection.direction_ba.avg_snr_db != null;
+ if(!hasDirectionAB || !hasDirectionBA){
+ continue;
+ }
+ }
+
+ // Apply minimum SNR filter
+ const configConnectionsMinSnrDb = getConfigConnectionsMinSnrDb();
+ if(configConnectionsMinSnrDb != null){
+ const snrAB = connection.direction_ab && connection.direction_ab.avg_snr_db != null ? connection.direction_ab.avg_snr_db : null;
+ const snrBA = connection.direction_ba && connection.direction_ba.avg_snr_db != null ? connection.direction_ba.avg_snr_db : null;
+
+ const configConnectionsBidirectionalMinSnr = getConfigConnectionsBidirectionalMinSnr();
+ let hasSnrAboveThreshold;
+
+ if(configConnectionsBidirectionalMinSnr){
+ // Bidirectional mode: ALL existing directions must meet threshold
+ const directionsToCheck = [];
+ if(snrAB != null) directionsToCheck.push(snrAB);
+ if(snrBA != null) directionsToCheck.push(snrBA);
+
+ if(directionsToCheck.length === 0){
+ // No SNR data in either direction, skip
+ hasSnrAboveThreshold = false;
+ } else {
+ // All existing directions must be above threshold
+ hasSnrAboveThreshold = directionsToCheck.every(snr => snr > configConnectionsMinSnrDb);
+ }
+ } else {
+ // Default mode: EITHER direction has SNR above threshold
+ hasSnrAboveThreshold = (snrAB != null && snrAB > configConnectionsMinSnrDb) || (snrBA != null && snrBA > configConnectionsMinSnrDb);
+ }
+
+ if(!hasSnrAboveThreshold){
+ continue;
+ }
+ }
+
// Calculate distance between nodes
const distanceInMeters = nodeAMarker.getLatLng().distanceTo(nodeBMarker.getLatLng()).toFixed(2);