Files
meshview/meshview/templates/node.html
2025-06-05 22:33:57 -07:00

235 lines
8.1 KiB
HTML

{% extends "base.html" %}
{% block css %}
/* Styles for the node info card */
#node_info {
height: 100%;
}
/* Styles for the map */
#map {
height: 100%;
min-height: 400px;
}
/* Styles for packet details section */
#packet_details {
height: 95vh;
overflow: scroll;
top: 3em;
}
/* Ensure inline display for details */
div.tab-pane > dl {
display: inline-block;
}
/* Set the maximum width of the page to 900px */
.container {
max-width: 900px;
margin: 0 auto; /* Center the content horizontally */
}
{% endblock %}
{% block body %}
<div id="node" class="container text-center">
<div class="container">
<div class="row">
<div class="col mb-3">
<!-- Node Information Card -->
<div class="card" id="node_info">
{% if node %}
<div class="card-header" id="node_color">
<strong style="margin-right: 1em ; margin-left: 1em; font-size: x-large;">{{node.short_name}}</strong>
<p style="margin-bottom: 0px; font-size: large; font-weight: bold;">{{node.long_name}}</p>
</div>
<div class="card-body">
<dl >
{% if trace %}
<dd id="map"></dd>
{% endif %}
<dt>NodeID</dt>
<dd>{{node.node_id|node_id_to_hex}}</dd>
<dt>Channel</dt>
<dd>{{node.channel}}</dd>
<dt>HW Model</dt>
<dd>{{node.hw_model}}</dd>
<dt>Role</dt>
<dd>{{node.role}}</dd>
{% if node.firmware %}
<dt>Firmware</dt>
<dd>{{node.firmware}}</dd>
{% endif %}
</dl>
<a href="/top?node_id={{node.node_id}}" >Get node traffic totals</a>
{% include "node_graphs.html" %}
</div>
{% else %}
<div class="card-body">
A NodeInfo has not been seen.
</div>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col">
<!-- Additional buttons can be included here -->
</div>
</div>
<div class="row">
<div class="col">
{% include 'packet_list.html' %}
</div>
<!-- <div class="col sticky-top" id="packet_details"></div> -->
</div>
</div>
</div>
<script>
var node_color = document.getElementById('node_color');
var node_id = '{{node.node_id | node_id_to_hex}}';
var color = node_id.slice(-6);
var bg_color = "#"+color;
node_color.style.background = bg_color;
var hex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(bg_color);
var text_color = [
parseInt(hex[1], 16),
parseInt(hex[2], 16),
parseInt(hex[3], 16),
];
const brightness = Math.round(((parseInt(text_color[0]) * 299) +
(parseInt(text_color[1]) * 587) +
(parseInt(text_color[2]) * 114)) / 1000);
if (brightness > 125) {
var textColor = '#212529'
node_color.style.color = textColor
}
</script>
{% if trace %}
<script>
var trace = {{ trace | tojson }}; // Load trace data into JavaScript
var map = L.map('map').setView(trace[0], 13); // Initialize map centered at first trace point
var markers = L.featureGroup(); // Create a feature group for markers
markers.addTo(map);
// Add tile layer (OpenStreetMap)
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Draw a polyline along the trace path
L.polyline(trace, { color: 'blue', weight: 1}).addTo(map);
// Add a red circle marker for the starting node with a tooltip
var startMarker = L.circleMarker(trace[0], {
radius: 8,
color: 'red',
weight: 1,
fillColor: 'red',
fillOpacity: 0.5
}).addTo(markers) // Add to feature group
.bindPopup(`
<b>{{node.long_name}}</b><br/>
<b>Short:</b> {{node.short_name}}<br/>
<b>Channel:</b> {{node.channel}}<br/>
<b>Hardware:</b> {{node.hw_model}}<br/>
<b>Role:</b> {{node.role}}<br/>
<b>Firmware:</b> {{node.firmware}}<br/>
<b>Coordinates:</b> [{{node.last_lat}} , {{node.last_long}}]
`, {permanent: false, direction: 'top', opacity: 0.9});
// Function to calculate distance and convert to miles
function getDistanceInMiles(latlng1, latlng2) {
var meters = latlng1.distanceTo(latlng2); // Get distance in meters
return meters * 0.000621371; // Convert meters to miles
}
{% for n in neighbors %}
var neighborLatLng = L.latLng([{{n.location[0]}}, {{n.location[1]}}]);
var startLatLng = L.latLng(trace[0]);
// Calculate distance in miles with 1 decimal place
var distanceMiles = getDistanceInMiles(startLatLng, neighborLatLng).toFixed(1);
// Create a blue circle marker for each neighbor node
var m = L.circleMarker(neighborLatLng, {
radius: 6,
color: 'blue',
weight: 1,
fillColor: 'blue',
fillOpacity: 0.5
}).addTo(markers) // Add to feature group
.bindPopup(`
<b><a href="/packet_list/{{n.node_id}}">{{n.long_name}}</a></b><br>
<b>{{n.short_name}}</b> <br/>
<b>SNR:</b> {{n.snr}} <br/>
<b>Distance:</b> ${distanceMiles} miles <br/>
`, {permanent: false, direction: 'top', opacity: 0.9});
// Draw a polyline from the first trace point to each neighbor node
L.polyline([startLatLng, neighborLatLng], {
color: 'grey',
weight: 1
}).addTo(map);
{% endfor %}
// Add a legend to the map
var legend = L.control({ position: 'bottomleft' });
legend.onAdd = function(map) {
var div = L.DomUtil.create('div', 'info legend');
div.style.background = 'white';
div.style.padding = '8px';
div.style.border = '1px solid black';
div.style.borderRadius = '5px';
div.style.boxShadow = '0 0 5px rgba(0,0,0,0.3)';
div.style.color = 'black'; // Ensure text is black
div.style.textAlign = 'left'; /* Ensure left alignment */
div.innerHTML = `
<b>Legend</b><br>
<svg width="16" height="16">
<circle cx="8" cy="8" r="6" fill="blue" stroke="blue" stroke-width="1" fill-opacity="0.4"/>
</svg> Neighbor Node<br>
<svg width="20" height="20">
<circle cx="10" cy="10" r="8" fill="red" stroke="red" stroke-width="1" fill-opacity="0.4"/>
</svg> Home Node<br>
<svg width="20" height="4">
<line x1="0" y1="2" x2="20" y2="2" stroke="grey" stroke-width="2"/>
</svg> Connection to Neighbors<br>
<svg width="20" height="4">
<line x1="0" y1="2" x2="20" y2="2" stroke="blue" stroke-width="2"/>
</svg> Path taken by node
`;
return div;
};
legend.addTo(map);
// Ensure the map adjusts to fit all markers and trace points
setTimeout(() => {
if (markers.getLayers().length > 0 || trace.length > 0) {
var bounds = markers.getBounds(); // Get bounds from markers
// Ensure trace points are included in the bounds
trace.forEach(point => {
bounds.extend(point);
});
map.fitBounds(bounds.pad(0.1), { maxZoom: 10 });
}
}, 200); // Slightly longer delay to ensure all elements are fully loaded
</script>
{% endif %}
{% endblock %}