Add bidirectional connection filtering and minimum SNR configuration to connections UI

This commit is contained in:
Anton Roslund 2026-01-10 13:43:11 +01:00
parent 4a4b5fb7f3
commit 1748079708

View file

@ -1379,6 +1379,31 @@
<div class="text-xs text-gray-600">Colors the connection lines by the average SNR in the worst direction. Reload to update map.</div> <div class="text-xs text-gray-600">Colors the connection lines by the average SNR in the worst direction. Reload to update map.</div>
</div> </div>
<!-- configConnectionsBidirectionalOnly -->
<div class="p-2">
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" v-model="configConnectionsBidirectionalOnly" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300" required>
</div>
<label class="ml-2 text-sm font-medium text-gray-900">Bidirectional Connections Only</label>
</div>
<div class="text-xs text-gray-600">Only show connections where data flows in both directions. Reload to update map.</div>
</div>
<!-- configConnectionsMinSnrDb -->
<div class="p-2">
<label class="block text-sm font-medium text-gray-900">Connections Minimum SNR (dB)</label>
<div class="text-xs text-gray-600 mb-2">Only show connections where at least one direction has SNR above this threshold. Leave empty to show all connections. Reload to update map.</div>
<input type="number" v-model="configConnectionsMinSnrDb" placeholder="e.g. -10" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
<div class="mt-2 flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" v-model="configConnectionsBidirectionalMinSnr" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300" required>
</div>
<label class="ml-2 text-sm font-medium text-gray-900">Bidirectional Minimum SNR</label>
</div>
<div class="text-xs text-gray-600 ml-6">If checked, all existing directions must meet the minimum SNR threshold (both directions if bidirectional, single direction if unidirectional).</div>
</div>
<!-- configConnectionsMaxDistanceInMeters --> <!-- configConnectionsMaxDistanceInMeters -->
<div class="p-2"> <div class="p-2">
<label class="block text-sm font-medium text-gray-900">Connections Max Distance (meters)</label> <label class="block text-sm font-medium text-gray-900">Connections Max Distance (meters)</label>
@ -1682,8 +1707,8 @@
function getConfigConnectionsTimePeriodInSeconds() { function getConfigConnectionsTimePeriodInSeconds() {
const value = localStorage.getItem("config_connections_time_period_in_seconds"); const value = localStorage.getItem("config_connections_time_period_in_seconds");
// default to 24 hours if unset // default to 7 days if unset
return value != null ? parseInt(value) : 86400; return value != null ? parseInt(value) : 604800;
} }
function setConfigConnectionsTimePeriodInSeconds(value) { function setConfigConnectionsTimePeriodInSeconds(value) {
@ -1703,6 +1728,51 @@
return localStorage.setItem("config_connections_colored_lines", value); 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() { function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
} }
@ -1726,6 +1796,9 @@
configTemperatureFormat: window.getConfigTemperatureFormat(), configTemperatureFormat: window.getConfigTemperatureFormat(),
configConnectionsTimePeriodInSeconds: window.getConfigConnectionsTimePeriodInSeconds(), configConnectionsTimePeriodInSeconds: window.getConfigConnectionsTimePeriodInSeconds(),
configConnectionsColoredLines: window.getConfigConnectionsColoredLines(), configConnectionsColoredLines: window.getConfigConnectionsColoredLines(),
configConnectionsBidirectionalOnly: window.getConfigConnectionsBidirectionalOnly(),
configConnectionsMinSnrDb: window.getConfigConnectionsMinSnrDb(),
configConnectionsBidirectionalMinSnr: window.getConfigConnectionsBidirectionalMinSnr(),
isShowingHardwareModels: false, isShowingHardwareModels: false,
hardwareModelStats: null, hardwareModelStats: null,
@ -2643,6 +2716,15 @@
configConnectionsColoredLines() { configConnectionsColoredLines() {
window.setConfigConnectionsColoredLines(this.configConnectionsColoredLines); window.setConfigConnectionsColoredLines(this.configConnectionsColoredLines);
}, },
configConnectionsBidirectionalOnly() {
window.setConfigConnectionsBidirectionalOnly(this.configConnectionsBidirectionalOnly);
},
configConnectionsMinSnrDb() {
window.setConfigConnectionsMinSnrDb(this.configConnectionsMinSnrDb);
},
configConnectionsBidirectionalMinSnr() {
window.setConfigConnectionsBidirectionalMinSnr(this.configConnectionsBidirectionalMinSnr);
},
deviceMetricsTimeRange() { deviceMetricsTimeRange() {
this.loadNodeDeviceMetrics(this.selectedNode.node_id); this.loadNodeDeviceMetrics(this.selectedNode.node_id);
}, },
@ -3214,6 +3296,48 @@
if (!otherNode || !otherNodeMarker) continue; 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 // Calculate distance
const distanceInMeters = nodeMarker.getLatLng().distanceTo(otherNodeMarker.getLatLng()).toFixed(2); const distanceInMeters = nodeMarker.getLatLng().distanceTo(otherNodeMarker.getLatLng()).toFixed(2);
const configConnectionsMaxDistanceInMeters = getConfigConnectionsMaxDistanceInMeters(); const configConnectionsMaxDistanceInMeters = getConfigConnectionsMaxDistanceInMeters();
@ -3611,6 +3735,48 @@
continue; 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 // Calculate distance between nodes
const distanceInMeters = nodeAMarker.getLatLng().distanceTo(nodeBMarker.getLatLng()).toFixed(2); const distanceInMeters = nodeAMarker.getLatLng().distanceTo(nodeBMarker.getLatLng()).toFixed(2);