mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-03-04 23:27:46 +01:00
started work on new traceroute template
This commit is contained in:
96
README.md
96
README.md
@@ -554,60 +554,76 @@ echo "Database cleanup completed on $(date)"
|
||||
- If you are using PostgreSQL, use this version instead (adjust credentials/DB name):
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
DB_NAME="meshview"
|
||||
DB_USER="meshview"
|
||||
DB_HOST="localhost"
|
||||
DB_PORT="5432"
|
||||
DB="postgresql://meshview@localhost:5432/meshview"
|
||||
RETENTION_DAYS=14
|
||||
BATCH_SIZE=100
|
||||
|
||||
# Stop DB service
|
||||
sudo systemctl stop meshview-db.service
|
||||
sudo systemctl stop meshview-web.service
|
||||
PSQL="/usr/bin/psql"
|
||||
|
||||
sleep 5
|
||||
echo "Run cleanup..."
|
||||
# Run cleanup queries
|
||||
psql "postgresql://${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}" <<'EOF'
|
||||
WITH deleted AS (
|
||||
DELETE FROM packet
|
||||
echo "[$(date)] Starting batched cleanup..."
|
||||
|
||||
while true; do
|
||||
DELETED=$(
|
||||
$PSQL "$DB" -At -v ON_ERROR_STOP=1 <<EOF
|
||||
WITH cutoff AS (
|
||||
SELECT (EXTRACT(EPOCH FROM (NOW() - INTERVAL '${RETENTION_DAYS} days')) * 1000000)::bigint AS ts
|
||||
),
|
||||
old_packets AS (
|
||||
SELECT id
|
||||
FROM packet, cutoff
|
||||
WHERE import_time_us IS NOT NULL
|
||||
AND import_time_us < (EXTRACT(EPOCH FROM (NOW() - INTERVAL '14 days')) * 1000000)
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT 'packet deleted: ' || COUNT(*) FROM deleted;
|
||||
|
||||
WITH deleted AS (
|
||||
AND import_time_us < cutoff.ts
|
||||
ORDER BY id
|
||||
LIMIT ${BATCH_SIZE}
|
||||
),
|
||||
ps_del AS (
|
||||
DELETE FROM packet_seen
|
||||
WHERE import_time_us IS NOT NULL
|
||||
AND import_time_us < (EXTRACT(EPOCH FROM (NOW() - INTERVAL '14 days')) * 1000000)
|
||||
WHERE packet_id IN (SELECT id FROM old_packets)
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT 'packet_seen deleted: ' || COUNT(*) FROM deleted;
|
||||
|
||||
WITH deleted AS (
|
||||
),
|
||||
tr_del AS (
|
||||
DELETE FROM traceroute
|
||||
WHERE import_time_us IS NOT NULL
|
||||
AND import_time_us < (EXTRACT(EPOCH FROM (NOW() - INTERVAL '14 days')) * 1000000)
|
||||
WHERE packet_id IN (SELECT id FROM old_packets)
|
||||
RETURNING 1
|
||||
),
|
||||
p_del AS (
|
||||
DELETE FROM packet
|
||||
WHERE id IN (SELECT id FROM old_packets)
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT 'traceroute deleted: ' || COUNT(*) FROM deleted;
|
||||
SELECT COUNT(*) FROM p_del;
|
||||
EOF
|
||||
)
|
||||
|
||||
WITH deleted AS (
|
||||
DELETE FROM node
|
||||
WHERE last_seen_us IS NULL
|
||||
OR last_seen_us < (EXTRACT(EPOCH FROM (NOW() - INTERVAL '14 days')) * 1000000)
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT 'node deleted: ' || COUNT(*) FROM deleted;
|
||||
if [[ "$DELETED" -eq 0 ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
VACUUM;
|
||||
echo "[$(date)] Packet cleanup complete"
|
||||
|
||||
echo "[$(date)] Cleaning old nodes..."
|
||||
|
||||
$PSQL "$DB" -v ON_ERROR_STOP=1 <<EOF
|
||||
DELETE FROM node
|
||||
WHERE last_seen_us IS NOT NULL
|
||||
AND last_seen_us < (
|
||||
EXTRACT(EPOCH FROM (NOW() - INTERVAL '${RETENTION_DAYS} days')) * 1000000
|
||||
);
|
||||
EOF
|
||||
|
||||
# Start DB service
|
||||
sudo systemctl start meshview-db.service
|
||||
sudo systemctl start meshview-web.service
|
||||
echo "[$(date)] Node cleanup complete"
|
||||
|
||||
echo "Database cleanup completed on $(date)"
|
||||
$PSQL "$DB" -c "VACUUM (ANALYZE) packet_seen;"
|
||||
$PSQL "$DB" -c "VACUUM (ANALYZE) traceroute;"
|
||||
$PSQL "$DB" -c "VACUUM (ANALYZE) packet;"
|
||||
$PSQL "$DB" -c "VACUUM (ANALYZE) node;"
|
||||
|
||||
echo "[$(date)] Cleanup finished"
|
||||
```
|
||||
- Schedule running the script on a regular basis.
|
||||
- In this example it runs every night at 2:00am.
|
||||
|
||||
138
meshview/templates/traceroute.html
Normal file
138
meshview/templates/traceroute.html
Normal file
@@ -0,0 +1,138 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
#traceroute-graph {
|
||||
width: 100%;
|
||||
height: 85vh;
|
||||
border: 1px solid #2a2f36;
|
||||
background: linear-gradient(135deg, #0f1216 0%, #171b22 100%);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#traceroute-meta {
|
||||
padding: 12px 16px;
|
||||
color: #c8d0da;
|
||||
}
|
||||
|
||||
#traceroute-error {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div id="traceroute-meta">
|
||||
<div><b>Traceroute</b> <span id="traceroute-title"></span></div>
|
||||
<div id="traceroute-error"></div>
|
||||
</div>
|
||||
<div id="traceroute-graph"></div>
|
||||
|
||||
<script>
|
||||
const el = document.getElementById("traceroute-graph");
|
||||
const chart = echarts.init(el);
|
||||
|
||||
function packetIdFromPath() {
|
||||
const parts = window.location.pathname.split("/").filter(Boolean);
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
|
||||
function addPathEdges(path, edges, style) {
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
edges.push({
|
||||
source: String(path[i]),
|
||||
target: String(path[i + 1]),
|
||||
lineStyle: style
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTraceroute() {
|
||||
const packetId = packetIdFromPath();
|
||||
document.getElementById("traceroute-title").textContent = `#${packetId}`;
|
||||
|
||||
const [res, nodesRes] = await Promise.all([
|
||||
fetch(`/api/traceroute/${packetId}`),
|
||||
fetch("/api/nodes"),
|
||||
]);
|
||||
if (!res.ok) {
|
||||
document.getElementById("traceroute-error").textContent = "Traceroute not found.";
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const nodesData = nodesRes.ok ? await nodesRes.json() : { nodes: [] };
|
||||
const nodeShortNameById = new Map(
|
||||
(nodesData.nodes || []).map(n => [String(n.node_id), n.short_name || n.long_name || String(n.node_id)])
|
||||
);
|
||||
const nodeLongNameById = new Map(
|
||||
(nodesData.nodes || []).map(n => [String(n.node_id), n.long_name || n.short_name || String(n.node_id)])
|
||||
);
|
||||
const nodes = new Map();
|
||||
const edges = [];
|
||||
|
||||
const forwardPaths = data?.winning_paths?.forward || [];
|
||||
const reversePaths = data?.winning_paths?.reverse || [];
|
||||
const originId = data?.packet?.from != null ? String(data.packet.from) : null;
|
||||
const targetId = data?.packet?.to != null ? String(data.packet.to) : null;
|
||||
|
||||
forwardPaths.forEach(path => {
|
||||
path.forEach(id => nodes.set(String(id), { name: String(id) }));
|
||||
addPathEdges(path, edges, { color: "#ff5733", width: 3 });
|
||||
});
|
||||
|
||||
reversePaths.forEach(path => {
|
||||
path.forEach(id => nodes.set(String(id), { name: String(id) }));
|
||||
addPathEdges(path, edges, { color: "#00c3ff", width: 2, type: "dashed" });
|
||||
});
|
||||
|
||||
const graphNodes = Array.from(nodes.values()).map(n => {
|
||||
const isOrigin = originId && n.name === originId;
|
||||
const isTarget = targetId && n.name === targetId;
|
||||
const color = isOrigin ? "#ff3b30" : isTarget ? "#34c759" : "#8aa4c8";
|
||||
const size = isOrigin || isTarget ? 44 : 36;
|
||||
return {
|
||||
id: n.name,
|
||||
name: nodeShortNameById.get(n.name) || n.name,
|
||||
symbolSize: size,
|
||||
itemStyle: { color },
|
||||
label: {
|
||||
show: true,
|
||||
color: "#e7eef7",
|
||||
fontWeight: "bold"
|
||||
},
|
||||
tooltip: {
|
||||
formatter: () => nodeLongNameById.get(n.name) || n.name
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const option = {
|
||||
backgroundColor: "transparent",
|
||||
tooltip: { trigger: "item" },
|
||||
series: [
|
||||
{
|
||||
type: "graph",
|
||||
layout: "force",
|
||||
roam: true,
|
||||
zoom: 1.2,
|
||||
draggable: true,
|
||||
force: { repulsion: 200, edgeLength: 80 },
|
||||
data: graphNodes,
|
||||
edges: edges,
|
||||
lineStyle: { opacity: 0.8, curveness: 0.1 },
|
||||
edgeSymbol: ["none", "arrow"],
|
||||
edgeSymbolSize: 10
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
chart.setOption(option);
|
||||
}
|
||||
|
||||
loadTraceroute();
|
||||
window.addEventListener("resize", () => chart.resize());
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -314,6 +314,15 @@ async def stats(request):
|
||||
)
|
||||
|
||||
|
||||
@routes.get("/traceroute/{packet_id}")
|
||||
async def traceroute_page(request):
|
||||
template = env.get_template("traceroute.html")
|
||||
return web.Response(
|
||||
text=template.render(),
|
||||
content_type="text/html",
|
||||
)
|
||||
|
||||
|
||||
# Keep !!
|
||||
@routes.get("/graph/traceroute/{packet_id}")
|
||||
async def graph_traceroute(request):
|
||||
|
||||
@@ -761,7 +761,8 @@ async def api_traceroute(request):
|
||||
|
||||
forward_paths = []
|
||||
reverse_paths = []
|
||||
winning_paths = []
|
||||
winning_forward_paths = []
|
||||
winning_reverse_paths = []
|
||||
|
||||
for tr in tr_groups:
|
||||
f = tuple(tr["forward_hops"])
|
||||
@@ -774,7 +775,10 @@ async def api_traceroute(request):
|
||||
reverse_paths.append(r)
|
||||
|
||||
if tr["done"]:
|
||||
winning_paths.append(f)
|
||||
if tr["forward_hops"]:
|
||||
winning_forward_paths.append(f)
|
||||
if tr["reverse_hops"]:
|
||||
winning_reverse_paths.append(r)
|
||||
|
||||
# Deduplicate
|
||||
unique_forward_paths = sorted(set(forward_paths))
|
||||
@@ -790,7 +794,30 @@ async def api_traceroute(request):
|
||||
|
||||
unique_reverse_paths_json = [list(p) for p in unique_reverse_paths]
|
||||
|
||||
winning_paths_json = [list(p) for p in set(winning_paths)]
|
||||
from_node_id = packet.from_node_id
|
||||
to_node_id = packet.to_node_id
|
||||
winning_forward_with_endpoints = []
|
||||
for path in set(winning_forward_paths):
|
||||
full_path = list(path)
|
||||
if from_node_id is not None and (not full_path or full_path[0] != from_node_id):
|
||||
full_path = [from_node_id, *full_path]
|
||||
if to_node_id is not None and (not full_path or full_path[-1] != to_node_id):
|
||||
full_path = [*full_path, to_node_id]
|
||||
winning_forward_with_endpoints.append(full_path)
|
||||
|
||||
winning_reverse_with_endpoints = []
|
||||
for path in set(winning_reverse_paths):
|
||||
full_path = list(path)
|
||||
if to_node_id is not None and (not full_path or full_path[0] != to_node_id):
|
||||
full_path = [to_node_id, *full_path]
|
||||
if from_node_id is not None and (not full_path or full_path[-1] != from_node_id):
|
||||
full_path = [*full_path, from_node_id]
|
||||
winning_reverse_with_endpoints.append(full_path)
|
||||
|
||||
winning_paths_json = {
|
||||
"forward": winning_forward_with_endpoints,
|
||||
"reverse": winning_reverse_with_endpoints,
|
||||
}
|
||||
|
||||
# --------------------------------------------
|
||||
# Final API output
|
||||
|
||||
Reference in New Issue
Block a user