|
|
|
|
@@ -1303,29 +1303,6 @@
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- configNodesDisconnectedAgeInSeconds -->
|
|
|
|
|
<div class="p-2">
|
|
|
|
|
<label class="block text-sm font-medium text-gray-900">Nodes Disconnected Age</label>
|
|
|
|
|
<div class="text-xs text-gray-600 mb-2">Nodes that have not uplinked to MQTT in this time will show as blue icons. Reload to update map.</div>
|
|
|
|
|
<select v-model="configNodesDisconnectedAgeInSeconds" 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">
|
|
|
|
|
<option value="900">15 minutes</option>
|
|
|
|
|
<option value="1800">30 minutes</option>
|
|
|
|
|
<option value="2700">45 minutes</option>
|
|
|
|
|
<option value="3600">1 hour</option>
|
|
|
|
|
<option value="7200">2 hours</option>
|
|
|
|
|
<option value="10800">3 hours</option>
|
|
|
|
|
<option value="21600">6 hours</option>
|
|
|
|
|
<option value="43200">12 hours</option>
|
|
|
|
|
<option value="86400">24 hours</option>
|
|
|
|
|
<option value="172800">2 days</option>
|
|
|
|
|
<option value="259200">3 days</option>
|
|
|
|
|
<option value="345600">4 days</option>
|
|
|
|
|
<option value="432000">5 days</option>
|
|
|
|
|
<option value="518400">6 days</option>
|
|
|
|
|
<option value="604800">7 days</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- configNodesOfflineAgeInSeconds -->
|
|
|
|
|
<div class="p-2">
|
|
|
|
|
<label class="block text-sm font-medium text-gray-900">Nodes Offline Age</label>
|
|
|
|
|
@@ -1374,8 +1351,8 @@
|
|
|
|
|
|
|
|
|
|
<!-- configConnectionsTimePeriodInSeconds -->
|
|
|
|
|
<div class="p-2">
|
|
|
|
|
<label class="block text-sm font-medium text-gray-900">Connections Time Period</label>
|
|
|
|
|
<div class="text-xs text-gray-600 mb-2">Edges within this time period are shown in the Connections layer. Reload to update map.</div>
|
|
|
|
|
<label class="block text-sm font-medium text-gray-900">Connections Max Age</label>
|
|
|
|
|
<div class="text-xs text-gray-600 mb-2">Edges from traceroutes and neighbour info within this time period are shown in the Connections layer. Reload to update map.</div>
|
|
|
|
|
<select v-model="configConnectionsTimePeriodInSeconds" 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">
|
|
|
|
|
<option value="300">5 minutes</option>
|
|
|
|
|
<option value="900">15 minutes</option>
|
|
|
|
|
@@ -1402,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>
|
|
|
|
|
|
|
|
|
|
<!-- 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 -->
|
|
|
|
|
<div class="p-2">
|
|
|
|
|
<label class="block text-sm font-medium text-gray-900">Connections Max Distance (meters)</label>
|
|
|
|
|
@@ -1657,20 +1659,6 @@
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getConfigNodesDisconnectedAgeInSeconds() {
|
|
|
|
|
// default to showing nodes as recently uplinked if heard in the last 30 minutes
|
|
|
|
|
const value = localStorage.getItem("config_nodes_disconnected_age_in_seconds");
|
|
|
|
|
return value != null ? parseInt(value) : 1800;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setConfigNodesDisconnectedAgeInSeconds(value) {
|
|
|
|
|
if(value != null){
|
|
|
|
|
return localStorage.setItem("config_nodes_disconnected_age_in_seconds", value);
|
|
|
|
|
} else {
|
|
|
|
|
return localStorage.removeItem("config_nodes_disconnected_age_in_seconds");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getConfigNodesOfflineAgeInSeconds() {
|
|
|
|
|
const value = localStorage.getItem("config_nodes_offline_age_in_seconds");
|
|
|
|
|
return value != null ? parseInt(value) : null;
|
|
|
|
|
@@ -1719,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) {
|
|
|
|
|
@@ -1740,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);
|
|
|
|
|
}
|
|
|
|
|
@@ -1754,7 +1787,6 @@
|
|
|
|
|
isShowingAnnouncement: this.shouldShowAnnouncement(),
|
|
|
|
|
|
|
|
|
|
configNodesMaxAgeInSeconds: window.getConfigNodesMaxAgeInSeconds(),
|
|
|
|
|
configNodesDisconnectedAgeInSeconds: window.getConfigNodesDisconnectedAgeInSeconds(),
|
|
|
|
|
configNodesOfflineAgeInSeconds: window.getConfigNodesOfflineAgeInSeconds(),
|
|
|
|
|
configWaypointsMaxAgeInSeconds: window.getConfigWaypointsMaxAgeInSeconds(),
|
|
|
|
|
configConnectionsMaxDistanceInMeters: window.getConfigConnectionsMaxDistanceInMeters(),
|
|
|
|
|
@@ -1764,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,
|
|
|
|
|
@@ -2654,9 +2689,6 @@
|
|
|
|
|
configNodesMaxAgeInSeconds() {
|
|
|
|
|
window.setConfigNodesMaxAgeInSeconds(this.configNodesMaxAgeInSeconds);
|
|
|
|
|
},
|
|
|
|
|
configNodesDisconnectedAgeInSeconds() {
|
|
|
|
|
window.setConfigNodesDisconnectedAgeInSeconds(this.configNodesDisconnectedAgeInSeconds);
|
|
|
|
|
},
|
|
|
|
|
configNodesOfflineAgeInSeconds() {
|
|
|
|
|
window.setConfigNodesOfflineAgeInSeconds(this.configNodesOfflineAgeInSeconds);
|
|
|
|
|
},
|
|
|
|
|
@@ -2684,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);
|
|
|
|
|
},
|
|
|
|
|
@@ -2867,7 +2908,7 @@
|
|
|
|
|
},
|
|
|
|
|
"Overlays": {
|
|
|
|
|
"Legend": legendLayerGroup,
|
|
|
|
|
"Backbone Connection": backboneConnectionsLayerGroup,
|
|
|
|
|
"Backbone Connections": backboneConnectionsLayerGroup,
|
|
|
|
|
"Connections": connectionsLayerGroup,
|
|
|
|
|
"Waypoints": waypointsLayerGroup,
|
|
|
|
|
"Position History": nodePositionHistoryLayerGroup,
|
|
|
|
|
@@ -3183,7 +3224,7 @@
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getTerrainProfileImage(node1, node2) {
|
|
|
|
|
function getTerrainProfileImage(url,node1, node2) {
|
|
|
|
|
|
|
|
|
|
// line colour between nodes
|
|
|
|
|
const lineColour = "0000FF"; // blue
|
|
|
|
|
@@ -3201,7 +3242,7 @@
|
|
|
|
|
const node2ElevationMSL = node2.altitude ?? "";
|
|
|
|
|
|
|
|
|
|
// generate terrain profile image url
|
|
|
|
|
return "https://heywhatsthat.com/bin/profile-0904.cgi?" + new URLSearchParams({
|
|
|
|
|
return url + new URLSearchParams({
|
|
|
|
|
src: "meshtastic.liamcottle.net",
|
|
|
|
|
axes: 1, // include grid lines and a scale
|
|
|
|
|
metric: 1, // show metric units
|
|
|
|
|
@@ -3255,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();
|
|
|
|
|
@@ -3340,13 +3423,6 @@
|
|
|
|
|
return string.replace(/</g, "<").replace(/>/g, ">");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// determine if node was recently heard uplinking packets to mqtt
|
|
|
|
|
function hasNodeUplinkedToMqttRecently(node) {
|
|
|
|
|
const now = moment();
|
|
|
|
|
const configNodesDisconnectedAgeInSeconds = getConfigNodesDisconnectedAgeInSeconds();
|
|
|
|
|
const millisecondsSinceNodeLastUplinkedToMqtt = now.diff(moment(node.mqtt_connection_state_updated_at));
|
|
|
|
|
return millisecondsSinceNodeLastUplinkedToMqtt < configNodesDisconnectedAgeInSeconds * 1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onNodesUpdated(updatedNodes) {
|
|
|
|
|
|
|
|
|
|
@@ -3416,12 +3492,6 @@
|
|
|
|
|
zIndexOffset = -1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// determine if node was recently heard uplinking packets to mqtt
|
|
|
|
|
//const nodeHasUplinkedToMqttRecently = hasNodeUplinkedToMqttRecently(node);
|
|
|
|
|
//if(nodeHasUplinkedToMqttRecently){
|
|
|
|
|
// icon = iconMqttConnected;
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
// To not have overlapping nodes.
|
|
|
|
|
var latJitter = 0;
|
|
|
|
|
var lonJitter = 0;
|
|
|
|
|
@@ -3636,9 +3706,13 @@
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add terrain profile image
|
|
|
|
|
const terrainImageUrl = getTerrainProfileImage(nodeA, nodeB);
|
|
|
|
|
tooltip += `<br/><br/>Terrain images from <a href="http://www.heywhatsthat.com" target="_blank">HeyWhatsThat.com</a>`;
|
|
|
|
|
const terrainImageUrl = getTerrainProfileImage("https://heywhatsthat.com/bin/profile-0904.cgi?", nodeA, nodeB);
|
|
|
|
|
tooltip += `<br/><br/>Terrain image from <a href="http://www.heywhatsthat.com" target="_blank">HeyWhatsThat.com</a>`;
|
|
|
|
|
tooltip += `<br/><a href="${terrainImageUrl}" target="_blank"><img src="${terrainImageUrl}" width="100%"></a>`;
|
|
|
|
|
|
|
|
|
|
const terrainImageUrl2 = getTerrainProfileImage("https://tjenavadärdetdär.se/?", nodeA, nodeB);
|
|
|
|
|
tooltip += `<br/>Terrain image from <a href="https://tjenavadärdetdär.se" target="_blank">TjenaVadÄrDetDär.se</a>`;
|
|
|
|
|
tooltip += `<br/><a href="${terrainImageUrl2}" target="_blank"><img src="${terrainImageUrl2}" width="100%"></a>`;
|
|
|
|
|
|
|
|
|
|
return tooltip;
|
|
|
|
|
}
|
|
|
|
|
@@ -3665,6 +3739,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);
|
|
|
|
|
|
|
|
|
|
@@ -3710,7 +3826,7 @@
|
|
|
|
|
event.target.closeTooltip();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// If both nodes are backbone nodes, also add to backbone layer group with arrows
|
|
|
|
|
// If both nodes are backbone nodes, also add to backbone layer group
|
|
|
|
|
if (nodeA.is_backbone && nodeB.is_backbone) {
|
|
|
|
|
const backboneLine = L.polyline([
|
|
|
|
|
nodeAMarker.getLatLng(),
|
|
|
|
|
@@ -3719,13 +3835,6 @@
|
|
|
|
|
color: lineColor,
|
|
|
|
|
opacity: 0.75,
|
|
|
|
|
weight: 3,
|
|
|
|
|
}).arrowheads({
|
|
|
|
|
size: '10px',
|
|
|
|
|
fill: true,
|
|
|
|
|
offsets: {
|
|
|
|
|
start: '25px',
|
|
|
|
|
end: '25px',
|
|
|
|
|
},
|
|
|
|
|
}).addTo(backboneConnectionsLayerGroup);
|
|
|
|
|
|
|
|
|
|
backboneLine.bindTooltip(tooltip, {
|
|
|
|
|
@@ -3784,7 +3893,7 @@
|
|
|
|
|
}
|
|
|
|
|
tooltip += `<br/>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}`;
|
|
|
|
|
tooltip += `<br/>${positionHistory.latitude}, ${positionHistory.longitude}`;
|
|
|
|
|
tooltip += `<br/>Heard on: ${moment(new Date(positionHistory.created_at)).format("DD/MM/YYYY hh:mm A")}`;
|
|
|
|
|
tooltip += `<br/>Heard on: ${moment(new Date(positionHistory.created_at)).format("YYYY-MM-DD HH:mm")}`;
|
|
|
|
|
|
|
|
|
|
// add gateway info if available
|
|
|
|
|
if(positionHistory.gateway_id){
|
|
|
|
|
@@ -3967,24 +4076,11 @@
|
|
|
|
|
|
|
|
|
|
function getTooltipContentForNode(node) {
|
|
|
|
|
|
|
|
|
|
// determine if node was recently heard uplinking packets to mqtt
|
|
|
|
|
const nodeHasUplinkedToMqttRecently = hasNodeUplinkedToMqttRecently(node);
|
|
|
|
|
var mqttStatus = `<span class="text-blue-700">Disconnected</span>`;
|
|
|
|
|
if(node.mqtt_connection_state_updated_at){
|
|
|
|
|
var mqttStatusUpdatedAt = moment(new Date(node.mqtt_connection_state_updated_at)).fromNow();
|
|
|
|
|
if(nodeHasUplinkedToMqttRecently){
|
|
|
|
|
mqttStatus = `<span><span class="text-green-700">Connected</span> (${mqttStatusUpdatedAt})</span>`;
|
|
|
|
|
} else {
|
|
|
|
|
mqttStatus = `<span><span class="text-blue-700">Disconnected</span> (${mqttStatusUpdatedAt})</span>`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var loraFrequencyRange = getRegionFrequencyRange(node.region_name);
|
|
|
|
|
|
|
|
|
|
var tooltip = `<img class="mb-4 w-40 mx-auto" src="/images/devices/${node.hardware_model_name}.png" onerror="this.classList.add('hidden')"/>` +
|
|
|
|
|
`<b>${escapeString(node.long_name)}</b>` +
|
|
|
|
|
`<br/>Short Name: ${escapeString(node.short_name)}` +
|
|
|
|
|
`<br/>MQTT: ${mqttStatus}` +
|
|
|
|
|
(node.num_online_local_nodes != null ? `<br/>Local Nodes Online: ${node.num_online_local_nodes}` : '') +
|
|
|
|
|
(node.position_precision != null && node.position_precision !== 32 ? `<br/>Position Precision: ${formatPositionPrecision(node.position_precision)}` : '') +
|
|
|
|
|
`<br/><br/>Role: ${node.role_name}` +
|
|
|
|
|
@@ -4021,6 +4117,7 @@
|
|
|
|
|
tooltip += `<br/><br/>ID: ${node.node_id}`;
|
|
|
|
|
tooltip += `<br/>Hex ID: ${node.node_id_hex}`;
|
|
|
|
|
tooltip += `<br/>Updated: ${moment(new Date(node.updated_at)).fromNow()}`;
|
|
|
|
|
tooltip += (node.mqtt_connection_state_updated_at ? `<br/>MQTT Updated: ${moment(new Date(node.mqtt_connection_state_updated_at)).fromNow()}` : '');
|
|
|
|
|
tooltip += (node.neighbours_updated_at ? `<br/>Neighbours Updated: ${moment(new Date(node.neighbours_updated_at)).fromNow()}` : '');
|
|
|
|
|
tooltip += (node.position_updated_at ? `<br/>Position Updated: ${moment(new Date(node.position_updated_at)).fromNow()}` : '');
|
|
|
|
|
|
|
|
|
|
|