mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-07-06 01:42:02 +02:00
map changes and other fixes
This commit is contained in:
+25
-5
@@ -19,6 +19,7 @@ logger = logging.getLogger(__name__)
|
|||||||
MQTT_GATEWAY_CACHE: set[int] = set()
|
MQTT_GATEWAY_CACHE: set[int] = set()
|
||||||
UNKNOWN_NODE_NAME = "unknown name"
|
UNKNOWN_NODE_NAME = "unknown name"
|
||||||
NODE_NAME_CONTROL_CHARS = re.compile(r"[\x00-\x1f\x7f]")
|
NODE_NAME_CONTROL_CHARS = re.compile(r"[\x00-\x1f\x7f]")
|
||||||
|
MQTT_DIRECT_NODE_ID = 1
|
||||||
|
|
||||||
|
|
||||||
def normalize_node_name(name: str) -> str:
|
def normalize_node_name(name: str) -> str:
|
||||||
@@ -28,6 +29,24 @@ def normalize_node_name(name: str) -> str:
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def storage_packet_id(packet) -> int | None:
|
||||||
|
if packet.id:
|
||||||
|
return packet.id
|
||||||
|
|
||||||
|
if packet.decoded.portnum != PortNum.MAP_REPORT_APP:
|
||||||
|
return None
|
||||||
|
|
||||||
|
from_node_id = getattr(packet, "from", 0) or 0
|
||||||
|
now_us = int(time.time() * 1_000_000)
|
||||||
|
return -(now_us * 2048 + (from_node_id & 0x7FF))
|
||||||
|
|
||||||
|
|
||||||
|
def storage_to_node_id(packet) -> int:
|
||||||
|
if packet.decoded.portnum == PortNum.MAP_REPORT_APP:
|
||||||
|
return MQTT_DIRECT_NODE_ID
|
||||||
|
return packet.to
|
||||||
|
|
||||||
|
|
||||||
async def capture_daily_snapshot() -> None:
|
async def capture_daily_snapshot() -> None:
|
||||||
today = datetime.now(UTC).date()
|
today = datetime.now(UTC).date()
|
||||||
|
|
||||||
@@ -147,20 +166,21 @@ async def process_envelope(topic, env):
|
|||||||
|
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
if not env.packet.id:
|
packet_id = storage_packet_id(env.packet)
|
||||||
|
if packet_id is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
async with mqtt_database.async_session() as session:
|
async with mqtt_database.async_session() as session:
|
||||||
# --- Packet insert with ON CONFLICT DO NOTHING
|
# --- Packet insert with ON CONFLICT DO NOTHING
|
||||||
result = await session.execute(select(Packet).where(Packet.id == env.packet.id))
|
result = await session.execute(select(Packet).where(Packet.id == packet_id))
|
||||||
packet = result.scalar_one_or_none()
|
packet = result.scalar_one_or_none()
|
||||||
if not packet:
|
if not packet:
|
||||||
now_us = int(time.time() * 1_000_000)
|
now_us = int(time.time() * 1_000_000)
|
||||||
packet_values = {
|
packet_values = {
|
||||||
"id": env.packet.id,
|
"id": packet_id,
|
||||||
"portnum": env.packet.decoded.portnum,
|
"portnum": env.packet.decoded.portnum,
|
||||||
"from_node_id": getattr(env.packet, "from"),
|
"from_node_id": getattr(env.packet, "from"),
|
||||||
"to_node_id": env.packet.to,
|
"to_node_id": storage_to_node_id(env.packet),
|
||||||
"payload": env.packet.SerializeToString(),
|
"payload": env.packet.SerializeToString(),
|
||||||
"import_time_us": now_us,
|
"import_time_us": now_us,
|
||||||
"channel": env.channel_id,
|
"channel": env.channel_id,
|
||||||
@@ -208,7 +228,7 @@ async def process_envelope(topic, env):
|
|||||||
|
|
||||||
now_us = int(time.time() * 1_000_000)
|
now_us = int(time.time() * 1_000_000)
|
||||||
seen_values = {
|
seen_values = {
|
||||||
"packet_id": env.packet.id,
|
"packet_id": packet_id,
|
||||||
"node_id": int(env.gateway_id[1:], 16),
|
"node_id": int(env.gateway_id[1:], 16),
|
||||||
"channel": env.channel_id,
|
"channel": env.channel_id,
|
||||||
"rx_time": env.packet.rx_time,
|
"rx_time": env.packet.rx_time,
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ function logPacketTimes(packet) {
|
|||||||
const times = formatTimes(packet.import_time_us);
|
const times = formatTimes(packet.import_time_us);
|
||||||
console.log(
|
console.log(
|
||||||
"[firehose] packet time",
|
"[firehose] packet time",
|
||||||
"id=" + packet.id,
|
"id=" + (packet.packet_id ?? packet.id),
|
||||||
"epoch_us=" + times.epoch,
|
"epoch_us=" + times.epoch,
|
||||||
"local=" + times.local,
|
"local=" + times.local,
|
||||||
"utc=" + times.utc
|
"utc=" + times.utc
|
||||||
@@ -372,6 +372,14 @@ async function fetchUpdates() {
|
|||||||
const safePayload = (pkt.payload || "")
|
const safePayload = (pkt.payload || "")
|
||||||
.replace(/</g, "<")
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, ">");
|
.replace(/>/g, ">");
|
||||||
|
const packetIdCell = pkt.packet_id
|
||||||
|
? `<a href="/packet/${pkt.storage_id || pkt.id}"
|
||||||
|
style="text-decoration:underline; color:inherit;">
|
||||||
|
${pkt.packet_id}
|
||||||
|
</a>`
|
||||||
|
: pkt.portnum === 73
|
||||||
|
? `<span class="text-secondary" title="MQTT map report">Not at Packet</span>`
|
||||||
|
: `<span class="text-secondary" title="No Meshtastic packet ID">—</span>`;
|
||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
<tr class="packet-row">
|
<tr class="packet-row">
|
||||||
@@ -382,10 +390,7 @@ async function fetchUpdates() {
|
|||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span class="toggle-btn">▶</span>
|
<span class="toggle-btn">▶</span>
|
||||||
<a href="/packet/${pkt.id}"
|
${packetIdCell}
|
||||||
style="text-decoration:underline; color:inherit;">
|
|
||||||
${pkt.id}
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>${from}</td>
|
<td>${from}</td>
|
||||||
|
|||||||
+134
-14
@@ -148,6 +148,7 @@
|
|||||||
<script src="https://unpkg.com/leaflet-polylinedecorator@1.6.0/dist/leaflet.polylinedecorator.js"
|
<script src="https://unpkg.com/leaflet-polylinedecorator@1.6.0/dist/leaflet.polylinedecorator.js"
|
||||||
integrity="sha384-FhPn/2P/fJGhQLeNWDn9B/2Gml2bPOrKJwFqJXgR3xOPYxWg5mYQ5XZdhUSugZT0"
|
integrity="sha384-FhPn/2P/fJGhQLeNWDn9B/2Gml2bPOrKJwFqJXgR3xOPYxWg5mYQ5XZdhUSugZT0"
|
||||||
crossorigin></script>
|
crossorigin></script>
|
||||||
|
<script src="https://unpkg.com/leaflet.heat/dist/leaflet-heat.js"></script>
|
||||||
<script src="/static/portmaps.js"></script>
|
<script src="/static/portmaps.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -188,20 +189,50 @@ function applyTranslationsMap(root = document) {
|
|||||||
====================================================== */
|
====================================================== */
|
||||||
|
|
||||||
var map = L.map('map');
|
var map = L.map('map');
|
||||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
var baseLayers = {
|
||||||
{ maxZoom:19, attribution:'© OpenStreetMap' }).addTo(map);
|
"OpenStreetMap": L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© OpenStreetMap'
|
||||||
|
}),
|
||||||
|
"OpenTopoMap": L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 17,
|
||||||
|
attribution: 'Map data: © OpenStreetMap, SRTM | Map style: © OpenTopoMap'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
baseLayers["OpenStreetMap"].addTo(map);
|
||||||
L.control.scale({
|
L.control.scale({
|
||||||
position: 'bottomleft',
|
position: 'bottomleft',
|
||||||
metric: true,
|
metric: true,
|
||||||
imperial: true
|
imperial: true
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
var nodeLayer = L.layerGroup().addTo(map);
|
||||||
|
var edgeLayer = L.layerGroup().addTo(map);
|
||||||
|
var trafficHeatLayer = L.heatLayer([], {
|
||||||
|
radius: 34,
|
||||||
|
blur: 18,
|
||||||
|
maxZoom: 12,
|
||||||
|
minOpacity: 0.3,
|
||||||
|
gradient: {
|
||||||
|
0.2: "#0b1f4d",
|
||||||
|
0.45: "#12436b",
|
||||||
|
0.7: "#7f2704",
|
||||||
|
1.0: "#4d0000"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var overlayLayers = {
|
||||||
|
"Nodes": nodeLayer,
|
||||||
|
"Traffic HeatMap": trafficHeatLayer
|
||||||
|
};
|
||||||
|
L.control.layers(baseLayers, overlayLayers, { collapsed: false }).addTo(map);
|
||||||
|
|
||||||
// Data structures
|
// Data structures
|
||||||
var nodes = [], markers = {}, markerById = {}, nodeMap = new Map();
|
var nodes = [], markers = {}, markerById = {}, nodeMap = new Map();
|
||||||
var edgeLayer = L.layerGroup().addTo(map), selectedNodeId = null;
|
var selectedNodeId = null;
|
||||||
var activeBlinks = new Map(), lastImportTime = null;
|
var activeBlinks = new Map(), lastImportTime = null;
|
||||||
var mapInterval = 0;
|
var mapInterval = 0;
|
||||||
var unmappedPackets = [];
|
var unmappedPackets = [];
|
||||||
|
var trafficHeatLoaded = false;
|
||||||
|
var trafficHeatLoading = false;
|
||||||
const UNMAPPED_LIMIT = 50;
|
const UNMAPPED_LIMIT = 50;
|
||||||
const UNMAPPED_TTL_MS = 5000;
|
const UNMAPPED_TTL_MS = 5000;
|
||||||
|
|
||||||
@@ -389,6 +420,15 @@ document.addEventListener("visibilitychange",()=>{
|
|||||||
document.hidden?stopPacketFetcher():startPacketFetcher();
|
document.hidden?stopPacketFetcher():startPacketFetcher();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
map.on("overlayadd", e => {
|
||||||
|
if(e.layer === nodeLayer) updateNodeVisibility();
|
||||||
|
if(e.layer === trafficHeatLayer) loadTrafficHeat();
|
||||||
|
});
|
||||||
|
|
||||||
|
map.on("overlayremove", e => {
|
||||||
|
if(e.layer === nodeLayer) clearActiveBlinks();
|
||||||
|
});
|
||||||
|
|
||||||
async function waitForConfig() {
|
async function waitForConfig() {
|
||||||
while (typeof window._siteConfigPromise === "undefined") {
|
while (typeof window._siteConfigPromise === "undefined") {
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
@@ -470,6 +510,9 @@ fetch('/api/nodes?days_active=3')
|
|||||||
|
|
||||||
renderNodesOnMap();
|
renderNodesOnMap();
|
||||||
createChannelFilters();
|
createChannelFilters();
|
||||||
|
if(map.hasLayer(trafficHeatLayer)){
|
||||||
|
loadTrafficHeat();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
|
||||||
@@ -490,7 +533,7 @@ function renderNodesOnMap(){
|
|||||||
fillColor: color,
|
fillColor: color,
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
weight: 0.7
|
weight: 0.7
|
||||||
}).addTo(map);
|
}).addTo(nodeLayer);
|
||||||
|
|
||||||
marker.nodeId = node.key;
|
marker.nodeId = node.key;
|
||||||
marker.originalColor = color;
|
marker.originalColor = color;
|
||||||
@@ -513,6 +556,56 @@ function renderNodesOnMap(){
|
|||||||
setTimeout(() => applyTranslationsMap(), 50);
|
setTimeout(() => applyTranslationsMap(), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
TRAFFIC HEAT LAYER
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
|
async function loadTrafficHeat(){
|
||||||
|
if(trafficHeatLoaded || trafficHeatLoading) return;
|
||||||
|
if(nodeMap.size === 0) return;
|
||||||
|
|
||||||
|
trafficHeatLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL("/api/packets", window.location.origin);
|
||||||
|
url.searchParams.set("limit", 1000);
|
||||||
|
|
||||||
|
const res = await fetch(url);
|
||||||
|
if(!res.ok) return;
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
const counts = new Map();
|
||||||
|
|
||||||
|
(data.packets || []).forEach(pkt => {
|
||||||
|
const nodeId = pkt.from_node_id;
|
||||||
|
if(nodeId == null) return;
|
||||||
|
counts.set(nodeId, (counts.get(nodeId) || 0) + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxCount = Math.max(...counts.values(), 0);
|
||||||
|
const heatPoints = [];
|
||||||
|
|
||||||
|
counts.forEach((count, nodeId) => {
|
||||||
|
const node = nodeMap.get(nodeId);
|
||||||
|
if(!node || isInvalidCoord(node)) return;
|
||||||
|
|
||||||
|
const markerLatLng = markerById[nodeId]?.getLatLng();
|
||||||
|
const lat = markerLatLng?.lat ?? node.lat;
|
||||||
|
const lng = markerLatLng?.lng ?? node.long;
|
||||||
|
const intensity = maxCount > 0 ? count / maxCount : 0;
|
||||||
|
|
||||||
|
heatPoints.push([lat, lng, Math.max(0.15, intensity)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
trafficHeatLayer.setLatLngs(heatPoints);
|
||||||
|
trafficHeatLoaded = true;
|
||||||
|
} catch(err) {
|
||||||
|
console.error("Failed to load traffic heat layer:", err);
|
||||||
|
} finally {
|
||||||
|
trafficHeatLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ======================================================
|
/* ======================================================
|
||||||
UNMAPPED PACKETS LIST
|
UNMAPPED PACKETS LIST
|
||||||
====================================================== */
|
====================================================== */
|
||||||
@@ -651,13 +744,33 @@ function isTextPort(portnum){
|
|||||||
return portnum === 1 || portnum === 7;
|
return portnum === 1 || portnum === 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNodeVisibleForPackets(marker){
|
||||||
|
return map.hasLayer(nodeLayer) && nodeLayer.hasLayer(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearBlinkForMarker(marker){
|
||||||
|
if(activeBlinks.has(marker)){
|
||||||
|
const interval = activeBlinks.get(marker);
|
||||||
|
clearInterval(interval);
|
||||||
|
activeBlinks.delete(marker);
|
||||||
|
}
|
||||||
|
marker.setStyle({ fillColor: marker.originalColor });
|
||||||
|
if(marker.tooltip){
|
||||||
|
map.removeLayer(marker.tooltip);
|
||||||
|
marker.tooltip = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearActiveBlinks(){
|
||||||
|
activeBlinks.forEach((interval, marker) => clearBlinkForMarker(marker));
|
||||||
|
activeBlinks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
function blinkNode(marker,longName,portnum,payload){
|
function blinkNode(marker,longName,portnum,payload){
|
||||||
if(!map.hasLayer(marker)) return;
|
if(!isNodeVisibleForPackets(marker)) return;
|
||||||
|
|
||||||
if(activeBlinks.has(marker)){
|
if(activeBlinks.has(marker)){
|
||||||
clearInterval(activeBlinks.get(marker));
|
clearBlinkForMarker(marker);
|
||||||
marker.setStyle({ fillColor: marker.originalColor });
|
|
||||||
if(marker.tooltip) map.removeLayer(marker.tooltip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let blinkCount = 0;
|
let blinkCount = 0;
|
||||||
@@ -685,19 +798,19 @@ function blinkNode(marker,longName,portnum,payload){
|
|||||||
marker.tooltip = tooltip;
|
marker.tooltip = tooltip;
|
||||||
|
|
||||||
const interval = setInterval(()=>{
|
const interval = setInterval(()=>{
|
||||||
if(map.hasLayer(marker)){
|
if(isNodeVisibleForPackets(marker)){
|
||||||
marker.setStyle({
|
marker.setStyle({
|
||||||
fillColor: blinkCount%2===0 ? 'yellow' : marker.originalColor
|
fillColor: blinkCount%2===0 ? 'yellow' : marker.originalColor
|
||||||
});
|
});
|
||||||
marker.bringToFront();
|
marker.bringToFront();
|
||||||
|
} else {
|
||||||
|
clearBlinkForMarker(marker);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
blinkCount++;
|
blinkCount++;
|
||||||
|
|
||||||
if(Date.now() - blinkStart > blinkDurationMs){
|
if(Date.now() - blinkStart > blinkDurationMs){
|
||||||
clearInterval(interval);
|
clearBlinkForMarker(marker);
|
||||||
marker.setStyle({ fillColor: marker.originalColor });
|
|
||||||
map.removeLayer(tooltip);
|
|
||||||
activeBlinks.delete(marker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},500);
|
},500);
|
||||||
@@ -757,6 +870,8 @@ function saveFiltersToLocalStorage(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeVisibility(){
|
function updateNodeVisibility(){
|
||||||
|
if(!map.hasLayer(nodeLayer)) return;
|
||||||
|
|
||||||
const routerOnly = document.getElementById("filter-routers-only").checked;
|
const routerOnly = document.getElementById("filter-routers-only").checked;
|
||||||
const mqttOnly = document.getElementById("filter-mqtt-only").checked;
|
const mqttOnly = document.getElementById("filter-mqtt-only").checked;
|
||||||
const activeChannels = [...channelSet].filter(ch =>
|
const activeChannels = [...channelSet].filter(ch =>
|
||||||
@@ -771,7 +886,12 @@ function updateNodeVisibility(){
|
|||||||
(!mqttOnly || n.is_mqtt_gateway) &&
|
(!mqttOnly || n.is_mqtt_gateway) &&
|
||||||
activeChannels.includes(n.channel);
|
activeChannels.includes(n.channel);
|
||||||
|
|
||||||
visible ? map.addLayer(marker) : map.removeLayer(marker);
|
if(visible){
|
||||||
|
nodeLayer.addLayer(marker);
|
||||||
|
} else {
|
||||||
|
clearBlinkForMarker(marker);
|
||||||
|
nodeLayer.removeLayer(marker);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1075,14 +1075,20 @@ async function loadPackets(filters = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sizeBytes = packetSizeBytes(pkt);
|
const sizeBytes = packetSizeBytes(pkt);
|
||||||
|
const packetIdCell = pkt.packet_id
|
||||||
|
? `<a href="/packet/${pkt.storage_id || pkt.id}"
|
||||||
|
style="text-decoration:underline; color:inherit;">
|
||||||
|
${pkt.packet_id}
|
||||||
|
</a>`
|
||||||
|
: pkt.portnum === 73
|
||||||
|
? `<span class="text-secondary" title="MQTT map report">Not a Packet</span>`
|
||||||
|
: `<span class="text-secondary" title="No Meshtastic packet ID">—</span>`;
|
||||||
|
|
||||||
list.insertAdjacentHTML("afterbegin", `
|
list.insertAdjacentHTML("afterbegin", `
|
||||||
<tr class="packet-row">
|
<tr class="packet-row">
|
||||||
<td>${localTime}</td>
|
<td>${localTime}</td>
|
||||||
<td><span class="toggle-btn">▶</span>
|
<td><span class="toggle-btn">▶</span>
|
||||||
<a href="/packet/${pkt.id}" style="text-decoration:underline; color:inherit;">
|
${packetIdCell}
|
||||||
${pkt.id}
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>${fromCell}</td>
|
<td>${fromCell}</td>
|
||||||
<td>${toCell}</td>
|
<td>${toCell}</td>
|
||||||
@@ -1706,6 +1712,7 @@ async function loadNodeStats(nodeId) {
|
|||||||
: "";
|
: "";
|
||||||
|
|
||||||
const portName = PORT_LABEL_MAP[pkt.portnum] || `Port ${pkt.portnum}`;
|
const portName = PORT_LABEL_MAP[pkt.portnum] || `Port ${pkt.portnum}`;
|
||||||
|
const displayPacketId = pkt.packet_id || (pkt.portnum === 73 ? "Not a Packet" : "");
|
||||||
|
|
||||||
// Escape quotes + line breaks for CSV safety
|
// Escape quotes + line breaks for CSV safety
|
||||||
const payload = (pkt.payload || "")
|
const payload = (pkt.payload || "")
|
||||||
@@ -1714,7 +1721,7 @@ async function loadNodeStats(nodeId) {
|
|||||||
|
|
||||||
rows.push([
|
rows.push([
|
||||||
time,
|
time,
|
||||||
pkt.id,
|
displayPacketId,
|
||||||
pkt.from_node_id,
|
pkt.from_node_id,
|
||||||
pkt.to_node_id,
|
pkt.to_node_id,
|
||||||
pkt.portnum,
|
pkt.portnum,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class Packet:
|
|||||||
"""UI-friendly packet wrapper for templates and API payloads."""
|
"""UI-friendly packet wrapper for templates and API payloads."""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
|
packet_id: int | None
|
||||||
from_node_id: int
|
from_node_id: int
|
||||||
from_node: models.Node
|
from_node: models.Node
|
||||||
to_node_id: int
|
to_node_id: int
|
||||||
@@ -99,8 +100,13 @@ class Packet:
|
|||||||
f'<a href="https://www.google.com/maps/search/?api=1&query={payload.latitude_i * 1e-7},{payload.longitude_i * 1e-7}" target="_blank">map</a>'
|
f'<a href="https://www.google.com/maps/search/?api=1&query={payload.latitude_i * 1e-7},{payload.longitude_i * 1e-7}" target="_blank">map</a>'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
packet_id = None
|
||||||
|
if mesh_packet and mesh_packet.id:
|
||||||
|
packet_id = mesh_packet.id
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
id=packet.id,
|
id=packet.id,
|
||||||
|
packet_id=packet_id,
|
||||||
from_node=packet.from_node,
|
from_node=packet.from_node,
|
||||||
from_node_id=packet.from_node_id,
|
from_node_id=packet.from_node_id,
|
||||||
to_node=packet.to_node,
|
to_node=packet.to_node,
|
||||||
|
|||||||
@@ -250,6 +250,8 @@ async def api_packets(request):
|
|||||||
p = Packet.from_model(packet)
|
p = Packet.from_model(packet)
|
||||||
data = {
|
data = {
|
||||||
"id": p.id,
|
"id": p.id,
|
||||||
|
"storage_id": p.id,
|
||||||
|
"packet_id": p.packet_id,
|
||||||
"from_node_id": p.from_node_id,
|
"from_node_id": p.from_node_id,
|
||||||
"to_node_id": p.to_node_id,
|
"to_node_id": p.to_node_id,
|
||||||
"portnum": int(p.portnum) if p.portnum is not None else None,
|
"portnum": int(p.portnum) if p.portnum is not None else None,
|
||||||
@@ -339,6 +341,8 @@ async def api_packets(request):
|
|||||||
for p in ui_packets:
|
for p in ui_packets:
|
||||||
packet_dict = {
|
packet_dict = {
|
||||||
"id": p.id,
|
"id": p.id,
|
||||||
|
"storage_id": p.id,
|
||||||
|
"packet_id": p.packet_id,
|
||||||
"import_time_us": p.import_time_us,
|
"import_time_us": p.import_time_us,
|
||||||
"channel": p.channel,
|
"channel": p.channel,
|
||||||
"from_node_id": p.from_node_id,
|
"from_node_id": p.from_node_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user