- Added the link to another sample instance
This commit is contained in:
Pablo Revilla
2025-03-28 14:05:48 -07:00
parent f5566ed435
commit 91ff7edbf9
4 changed files with 72 additions and 87 deletions

View File

@@ -144,14 +144,6 @@ async def get_total_packet_count():
result = await session.execute(q)
return result.scalar() # Return the total count of packets
# We count the total amount of nodes
async def get_total_node_count():
async with database.async_session() as session:
q = select(func.count(Node.id)) # Use SQLAlchemy's func to count nodes
q = q.where(Node.last_update > datetime.datetime.now() - datetime.timedelta(days=1)) # Look for nodes with nodeinfo updates in the last 24 hours
result = await session.execute(q)
return result.scalar() # Return the total count of nodes
# We count the total amount of seen packets
async def get_total_packet_seen_count():
async with database.async_session() as session:
@@ -160,59 +152,24 @@ async def get_total_packet_seen_count():
return result.scalar() # Return the total count of seen packets
async def get_total_node_count_longfast() -> int:
async def get_total_node_count(channel: str = None) -> int:
try:
# Open an asynchronous session with the database
async with database.async_session() as session:
# Build the query to count nodes where channel == 'LongFast'
q = select(func.count(Node.id))
q = q.where(Node.last_update > datetime.datetime.now() - datetime.timedelta( days=1)) # Look for nodes with nodeinfo updates in the last 24 hours
q = q.where(Node.channel == 'LongFast') #
q = select(func.count(Node.id)).where(
Node.last_update > datetime.datetime.now() - datetime.timedelta(days=1)
)
if channel:
q = q.where(Node.channel == channel)
# Execute the query asynchronously and fetch the result
result = await session.execute(q)
# Return the scalar value (the count of nodes)
return result.scalar()
except Exception as e:
# Log or handle the exception if needed (optional, replace with logging if necessary)
print(f"An error occurred: {e}")
return 0 # Return 0 or an appropriate fallback value in case of an error
return 0
async def get_total_node_count_mediumslow() -> int:
try:
# Open an asynchronous session with the database
async with database.async_session() as session:
# Build the query to count nodes where channel == 'LongFast'
q = select(func.count(Node.id))
q = q.where(Node.last_update > datetime.datetime.now() - datetime.timedelta(
days=1)) # Look for nodes with nodeinfo updates in the last 24 hours
q = q.where(Node.channel == 'MediumSlow') #
# Execute the query asynchronously and fetch the result
result = await session.execute(q)
# Return the scalar value (the count of nodes)
return result.scalar()
except Exception as e:
# Log or handle the exception if needed (optional, replace with logging if necessary)
print(f"An error occurred: {e}")
return 0 # Return 0 or an appropriate fallback value in case of an error
# Get Nodes for mediumslow only
# p.r.
async def get_nodes_mediumslow():
async with database.async_session() as session:
result = await session.execute(
select(Node)
.where(
(Node.channel == "MediumSlow")
)
)
return result.scalars()
async def get_top_traffic_nodes():
async with database.async_session() as session:
result = await session.execute(text("""
@@ -312,3 +269,4 @@ async def get_nodes(role=None, channel=None, hw_model=None):
except Exception as e:
print("error reading DB") # Consider using logging instead of print
return [] # Return an empty list in case of failure

View File

@@ -37,12 +37,20 @@
<body hx-indicator="#spinner">
<br><div style="text-align:center"><strong>{{ site_config["site"]["title"] }} {{ site_config["site"]["domain"] }}</strong></div>
<div style="text-align: center;">{{ site_config["site"]["message"] }}</div>
<div style="text-align:center">Quick Links:&nbsp;&nbsp;<a href="/nodelist">Nodes</a>&nbsp;-&nbsp;<a href="/chat">Conversations</a>&nbsp;-&nbsp;<a href="/firehose">See <strong>everything</strong> </a>
&nbsp;-&nbsp;Mesh Graph <a href="/nodegraph/LongFast">LF</a>&nbsp;-&nbsp;<a href="/nodegraph/MediumSlow">MS </a>&nbsp;-&nbsp;<a href="/stats">Stats </a>
&nbsp;-&nbsp;<a href="/net">Weekly Net</a>&nbsp;-&nbsp;<a href="/map">Map</a>&nbsp;-&nbsp;<a href="/top">Top Traffic</a></div><br>
<div id="spinner" class="spinner-border secondary-primary htmx-indicator position-absolute top-50 start-50" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div style="text-align:center">Quick Links:&nbsp;&nbsp;
{% if site_config["site"]["nodes"] == "True" %}<a href="/nodelist">Nodes</a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["conversations"] == "True" %}<a href="/chat">Conversations</a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["everything"] == "True" %}<a href="/firehose">See <strong>everything</strong></a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["graph_lf"] == "True" %} Mesh Graph: <a href="/nodegraph/LongFast">LF</a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["graph_ms"] == "True" %}<a href="/nodegraph/MediumSlow">MS</a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["graph_mf"] == "True" %}<a href="/nodegraph/MediumFast">MF</a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["net"] == "True" %}<a href="/net">Weekly Net</a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["map"] == "True" %}<a href="/map">Map</a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["stats"] == "True" %}<a href="/stats">Stats</a>&nbsp;-&nbsp;{% endif %}
{% if site_config["site"]["top"] == "True" %}<a href="/top">Top Traffic</a>{% endif %}
</div><br>
{% block body %}
{% endblock %}
<br><div style="text-align:center">Visit <strong><a href="https://github.com/pablorevilla-meshtastic/meshview">Meshview</a></strong> on Github. Also visit the original <a href="https://github.com/armooo/meshview">Meshview</a> by Armooo.</div><br>

View File

@@ -34,10 +34,7 @@
<div id="map" style="width: 100%; height: 600px;"></div>
<div id="filter-container">
<input type="checkbox" class="filter-checkbox" id="filter-longfast" checked> LongFast
<input type="checkbox" class="filter-checkbox" id="filter-mediumslow" checked> MediumSlow
<input type="checkbox" class="filter-checkbox" id="filter-router-longfast" checked> LongFast Routers
<input type="checkbox" class="filter-checkbox" id="filter-router-mediumslow" checked> MediumSlow Routers
<!-- Filters will be dynamically generated here -->
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
@@ -52,8 +49,9 @@
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
var markers = { LongFast: [], MediumSlow: [], RouterLongFast: [], RouterMediumSlow: [] };
var markers = {};
var bounds = L.latLngBounds();
var channels = new Set(); // Stores unique channels
var nodes = [
{% for node in nodes %}
@@ -86,18 +84,33 @@
return seconds + "s";
}
// Function to generate a unique color from a string (channel name)
function hashToColor(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let hue = hash % 360; // Keep hue in 0-360 range
return `hsl(${hue}, 80%, 50%)`; // Use HSL for vibrant colors
}
nodes.forEach(function(node) {
if (node.lat !== null && node.long !== null) {
let category = node.channel.toLowerCase().includes("mediumslow") ? "MediumSlow" : "LongFast";
let color = category === "MediumSlow" ? "#0000ff" : "#ff0000";
let category = node.channel;
let isRouter = node.role.toLowerCase().includes("router");
// Store the unique channel
channels.add(category);
// Generate a unique color for the channel
let color = hashToColor(category);
let markerOptions = {
radius: isRouter ? 9 : 7,
color: "white",
fillColor: color,
fillOpacity: 1,
weight: .7,
weight: 0.7,
};
var popupContent = `
@@ -110,40 +123,43 @@
if (node.firmware) popupContent += `<b>Firmware:</b> ${node.firmware}<br>`;
var marker = L.circleMarker([node.lat, node.long], markerOptions).bindPopup(popupContent);
marker.addTo(map);
if (isRouter) {
if (category === "LongFast") {
markers.RouterLongFast.push(marker);
} else {
markers.RouterMediumSlow.push(marker);
}
} else {
markers[category].push(marker);
}
if (!markers[category]) markers[category] = [];
markers[category].push(marker);
bounds.extend(marker.getLatLng());
}
});
// Fit map to bounds
var bayAreaBounds = [
[{{ site_config["site"]["map_top_left_lat"] }}, {{ site_config["site"]["map_top_left_lon"] }}],
[{{ site_config["site"]["map_bottom_right_lat"] }}, {{ site_config["site"]["map_bottom_right_lon"] }}]
];
map.fitBounds(bayAreaBounds);
// Generate dynamic filters
let filterContainer = document.getElementById("filter-container");
channels.forEach(channel => {
let filterId = `filter-${channel.replace(/\s+/g, '-').toLowerCase()}`;
let color = hashToColor(channel);
let filterHtml = `
<label style="color:${color};">
<input type="checkbox" class="filter-checkbox" id="${filterId}" checked> ${channel}
</label>
`;
filterContainer.innerHTML += filterHtml;
});
function updateMarkers() {
let showLongFast = document.getElementById("filter-longfast").checked;
let showMediumSlow = document.getElementById("filter-mediumslow").checked;
let showRouterLongFast = document.getElementById("filter-router-longfast").checked;
let showRouterMediumSlow = document.getElementById("filter-router-mediumslow").checked;
channels.forEach(channel => {
let filterId = `filter-${channel.replace(/\s+/g, '-').toLowerCase()}`;
let isChecked = document.getElementById(filterId).checked;
markers.LongFast.forEach(m => showLongFast ? map.addLayer(m) : map.removeLayer(m));
markers.MediumSlow.forEach(m => showMediumSlow ? map.addLayer(m) : map.removeLayer(m));
markers.RouterLongFast.forEach(m => showRouterLongFast ? map.addLayer(m) : map.removeLayer(m));
markers.RouterMediumSlow.forEach(m => showRouterMediumSlow ? map.addLayer(m) : map.removeLayer(m));
markers[channel].forEach(m => isChecked ? map.addLayer(m) : map.removeLayer(m));
});
}
document.querySelectorAll(".filter-checkbox").forEach(input => {
@@ -151,4 +167,5 @@
});
</script>
{% endblock %}

View File

@@ -26,6 +26,8 @@ import gc
from meshview import config
import json
from meshview.store import get_total_node_count
CONFIG = config.CONFIG
env = Environment(loader=PackageLoader("meshview"), autoescape=select_autoescape())
@@ -1191,8 +1193,8 @@ async def stats(request):
total_packets = await store.get_total_packet_count()
total_nodes = await store.get_total_node_count()
total_packets_seen = await store.get_total_packet_seen_count()
total_nodes_longfast = await store.get_total_node_count_longfast()
total_nodes_mediumslow = await store.get_total_node_count_mediumslow()
total_nodes_longfast = await get_total_node_count("LongFast")
total_nodes_mediumslow = await get_total_node_count("MediumSlow")
print_memory_usage()
template = env.get_template("stats.html")
return web.Response(
@@ -1208,7 +1210,7 @@ async def stats(request):
)
except Exception as e:
return web.Response(
text="An error occurred while processing your request.",
text=f"An error occurred: {str(e)}",
status=500,
content_type="text/plain",
)