From d9b1d5ac49c7ad769022c98570334d473915d799 Mon Sep 17 00:00:00 2001 From: Pablo Revilla Date: Wed, 22 Oct 2025 14:31:07 -0700 Subject: [PATCH] worked on making map and base all API driven --- meshview/templates/base.html | 155 +++++++++++-------------------- meshview/templates/chat.html | 58 +++++------- meshview/templates/firehose.html | 34 +++---- meshview/templates/map.html | 21 ++++- meshview/web.py | 38 +------- 5 files changed, 114 insertions(+), 192 deletions(-) diff --git a/meshview/templates/base.html b/meshview/templates/base.html index bcc0425..eb48105 100644 --- a/meshview/templates/base.html +++ b/meshview/templates/base.html @@ -27,11 +27,8 @@
-
- - @@ -57,10 +54,7 @@ {% endfor %} - - + @@ -69,105 +63,68 @@
-
ver. unknown
+
ver. unknown

- + - - - +async function loadSiteConfig() { + try { + const cfg = await window._siteConfigPromise; + const site = cfg.site || {}; + + // Title + document.title = "Meshview - " + (site.title || ""); + + // Header + const header = document.getElementById("site-header"); + if (header) + header.innerHTML = `${site.title || ""} ${site.domain ? "(" + site.domain + ")" : ""}`; + + // Message + const msg = document.getElementById("site-message"); + if (msg) msg.textContent = site.message || ""; + + // Menu + const menu = document.getElementById("site-menu"); + if (menu) { + let html = ""; + if (site.nodes === "true") html += `Nodes`; + if (site.conversations === "true") html += ` - Conversations`; + if (site.everything === "true") html += ` - See Everything`; + if (site.graphs === "true") html += ` - Mesh Graphs`; + if (site.net === "true") html += ` - Weekly Net`; + if (site.map === "true") html += ` - Live Map`; + if (site.stats === "true") html += ` - Stats`; + if (site.top === "true") html += ` - Top Traffic Nodes`; + menu.innerHTML = html; + } + + // Version + const verEl = document.getElementById("site-version"); + if (verEl) verEl.textContent = "ver. " + (site.version || "unknown"); + } catch (err) { + console.error("Failed to load site config:", err); + } +} + +document.addEventListener("DOMContentLoaded", loadSiteConfig); + diff --git a/meshview/templates/chat.html b/meshview/templates/chat.html index 3df51a9..24d333d 100644 --- a/meshview/templates/chat.html +++ b/meshview/templates/chat.html @@ -1,20 +1,14 @@ {% extends "base.html" %} {% block css %} -.timestamp { - min-width: 10em; -} +.timestamp { min-width: 10em; } .chat-packet:nth-of-type(odd) { background-color: #3a3a3a; } .chat-packet { border-bottom: 1px solid #555; padding: 8px; border-radius: 8px; } .chat-packet:nth-of-type(even) { background-color: #333333; } -@keyframes flash { - 0% { background-color: #ffe066; } - 100% { background-color: inherit; } -} +@keyframes flash { 0% { background-color: #ffe066; } 100% { background-color: inherit; } } .chat-packet.flash { animation: flash 3.5s ease-out; } -/* Nested reply style */ .replying-to { font-size: 0.85em; color: #aaa; margin-top: 4px; padding-left: 20px; } .replying-to .reply-preview { color: #aaa; } {% endblock %} @@ -36,11 +30,10 @@ document.addEventListener("DOMContentLoaded", async () => { function escapeHtml(text) { const div = document.createElement("div"); - div.textContent = text == null ? "" : text; + div.textContent = text ?? ""; return div.innerHTML; } - // 🔑 helper to apply translations function applyTranslations(translations, root=document) { root.querySelectorAll("[data-translate-lang]").forEach(el => { const key = el.dataset.translateLang; @@ -58,7 +51,7 @@ document.addEventListener("DOMContentLoaded", async () => { packetMap.set(packet.id, packet); const date = new Date(packet.import_time); - const formattedTime = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", second: "2-digit", hour12: true }); + const formattedTime = date.toLocaleTimeString([], { hour:"numeric", minute:"2-digit", second:"2-digit", hour12:true }); const formattedDate = `${(date.getMonth()+1).toString().padStart(2,"0")}/${date.getDate().toString().padStart(2,"0")}/${date.getFullYear()}`; const formattedTimestamp = `${formattedTime} - ${formattedDate}`; @@ -66,22 +59,18 @@ document.addEventListener("DOMContentLoaded", async () => { if (packet.reply_id) { const parent = packetMap.get(packet.reply_id); if (parent) { - replyHtml = ` -
-
- - ${escapeHtml((parent.long_name || "").trim() || `Node ${parent.from_node_id}`)}: - ${escapeHtml(parent.payload || "")} -
-
- `; - } else { - replyHtml = ` -
+ replyHtml = `
+
- ${packet.reply_id} + ${escapeHtml((parent.long_name || "").trim() || `Node ${parent.from_node_id}`)}: + ${escapeHtml(parent.payload || "")}
- `; +
`; + } else { + replyHtml = ``; } } @@ -91,7 +80,7 @@ document.addEventListener("DOMContentLoaded", async () => { div.innerHTML = ` ${formattedTimestamp} - ✉️ + ✉️ ${escapeHtml(packet.channel || "")} @@ -102,17 +91,15 @@ document.addEventListener("DOMContentLoaded", async () => { ${escapeHtml(packet.payload)}${replyHtml} `; chatContainer.prepend(div); - - // Apply translations to the newly added packet applyTranslations(chatTranslations, div); if (highlight) setTimeout(() => div.classList.remove("flash"), 2500); } - function renderPacketsEnsureDescending(packets, highlight = false) { - if (!Array.isArray(packets) || packets.length === 0) return; - const sortedDesc = packets.slice().sort((a,b) => new Date(b.import_time)-new Date(a.import_time)); - for (let i = sortedDesc.length-1; i>=0; i--) renderPacket(sortedDesc[i], highlight); + function renderPacketsEnsureDescending(packets, highlight=false) { + if (!Array.isArray(packets) || packets.length===0) return; + const sortedDesc = packets.slice().sort((a,b)=>new Date(b.import_time)-new Date(a.import_time)); + for (let i=sortedDesc.length-1; i>=0; i--) renderPacket(sortedDesc[i], highlight); } async function fetchInitial() { @@ -121,14 +108,14 @@ document.addEventListener("DOMContentLoaded", async () => { const data = await resp.json(); if (data?.packets?.length) renderPacketsEnsureDescending(data.packets); lastTime = data?.latest_import_time || lastTime; - } catch(err) { console.error("Initial fetch error:", err); } + } catch(err){ console.error("Initial fetch error:", err); } } async function fetchUpdates() { try { const url = new URL("/api/chat", window.location.origin); url.searchParams.set("limit","100"); - if(lastTime) url.searchParams.set("since", lastTime); + if (lastTime) url.searchParams.set("since", lastTime); const resp = await fetch(url); const data = await resp.json(); if (data?.packets?.length) renderPacketsEnsureDescending(data.packets, true); @@ -138,7 +125,8 @@ document.addEventListener("DOMContentLoaded", async () => { async function loadTranslations() { try { - const langCode = "{{ site_config.get('site', {}).get('language','en') }}"; + const cfg = await window._siteConfigPromise; + const langCode = cfg?.site?.language || "en"; const res = await fetch(`/api/lang?lang=${langCode}§ion=chat`); chatTranslations = await res.json(); applyTranslations(chatTranslations, document); diff --git a/meshview/templates/firehose.html b/meshview/templates/firehose.html index 3319b8f..faa0732 100644 --- a/meshview/templates/firehose.html +++ b/meshview/templates/firehose.html @@ -178,19 +178,17 @@ function portLabel(portnum, payload) { (${portnum})`; } -// --- Load config --- -async function loadConfig() { +// --- Fetch firehose interval from shared site config --- +async function configureFirehose() { try { - const res = await fetch("/api/config"); - if (!res.ok) return; - const cfg = await res.json(); + const cfg = await window._siteConfigPromise; const intervalSec = cfg?.site?.firehose_interval; if (intervalSec && !isNaN(intervalSec)) { updateInterval = parseInt(intervalSec) * 1000; } console.log("Firehose update interval:", updateInterval, "ms"); } catch (err) { - console.warn("Failed to load /api/config:", err); + console.warn("Failed to read firehose interval:", err); } } @@ -214,19 +212,12 @@ async function fetchUpdates() { const fromNodeId = pkt.from_node_id; const toNodeId = pkt.to_node_id; - let from = nodeName(fromNodeId); - if (fromNodeId !== 4294967295 && fromNodeId !== 1) { - from = `${nodeMap[fromNodeId]}`; - } + let from = fromNodeId === 4294967295 ? `All` : + `${nodeMap[fromNodeId] || fromNodeId}`; - let to = ""; - if (toNodeId === 1) { - to = `direct to MQTT`; - } else if (toNodeId === 4294967295) { - to = `All`; - } else { - to = `${nodeMap[toNodeId]}`; - } + let to = toNodeId === 1 ? `direct to MQTT` : + toNodeId === 4294967295 ? `All` : + `${nodeMap[toNodeId] || toNodeId}`; let links = ""; if (pkt.portnum === 3 && pkt.payload) { @@ -248,7 +239,6 @@ async function fetchUpdates() { } const safePayload = (pkt.payload || "").replace(//g, ">"); - const html = ` ${pkt.id} @@ -260,7 +250,6 @@ async function fetchUpdates() { ${safePayload} `; - list.insertAdjacentHTML("afterbegin", html); } @@ -288,10 +277,11 @@ document.addEventListener("DOMContentLoaded", async () => { btn.textContent = row.classList.contains("expanded") ? "▼" : "▶"; }); - await loadConfig(); + await configureFirehose(); await loadNodes(); fetchUpdates(); setInterval(fetchUpdates, updateInterval); }); -{% endblock %} + +{% endblock %} \ No newline at end of file diff --git a/meshview/templates/map.html b/meshview/templates/map.html index be79a9b..6fc6f3f 100644 --- a/meshview/templates/map.html +++ b/meshview/templates/map.html @@ -59,11 +59,28 @@ function timeAgo(date){ const diff=Date.now()-new Date(date), s=Math.floor(diff/ function hashToColor(str){ if(colorMap.has(str)) return colorMap.get(str); const c=palette[nextColorIndex++%palette.length]; colorMap.set(str,c); return c; } function isInvalidCoord(n){ return !n||!n.lat||!n.long||n.lat===0||n.long===0||Number.isNaN(n.lat)||Number.isNaN(n.long); } + +// ---------------------- WAIT FOR CONFIG ---------------------- +async function waitForConfig() { + while (typeof window._siteConfigPromise === "undefined") { + console.log("Waiting for _siteConfigPromise..."); + await new Promise(r => setTimeout(r, 100)); + } + + try { + const cfg = await window._siteConfigPromise; + if (!cfg || !cfg.site) throw new Error("Config missing site object"); + return cfg.site; + } catch (err) { + console.error("Error loading site config:", err); + return {}; + } +} + // ---------------------- Load Config & Start Polling ---------------------- async function initMapPolling() { try { - const cfg = await fetch('/api/config').then(r => r.json()); - const site = cfg.site || {}; + const site = await waitForConfig(); mapInterval = parseInt(site.map_interval, 10) || 0; // ---- Check URL params ---- diff --git a/meshview/web.py b/meshview/web.py index 4db59e9..afa946d 100644 --- a/meshview/web.py +++ b/meshview/web.py @@ -1114,38 +1114,10 @@ async def net(request): @routes.get("/map") async def map(request): - try: - # Parse optional URL parameters for custom view - map_center_lat = request.query.get("lat") - map_center_lng = request.query.get("lng") - map_zoom = request.query.get("zoom") - - # Validate and convert parameters if provided - custom_view = None - if map_center_lat and map_center_lng: - try: - lat = float(map_center_lat) - lng = float(map_center_lng) - zoom = int(map_zoom) if map_zoom else 13 - custom_view = {"lat": lat, "lng": lng, "zoom": zoom} - except (ValueError, TypeError): - # Invalid parameters, ignore and use defaults - pass - - template = env.get_template("map.html") - - return web.Response( - text=template.render( - custom_view=custom_view, - ), - content_type="text/html", - ) - except Exception as e: - print(f"/map route error: {e}") + template = env.get_template("map.html") return web.Response( - text="An error occurred while processing your request.", - status=500, - content_type="text/plain", + text=template.render(), + content_type="text/html" ) @@ -1257,7 +1229,7 @@ async def chat(request): try: template = env.get_template("chat.html") return web.Response( - text=template.render(site_config=CONFIG, SOFTWARE_RELEASE=SOFTWARE_RELEASE), + text=template.render(), content_type="text/html", ) except Exception as e: @@ -1266,8 +1238,6 @@ async def chat(request): rendered = template.render( error_message="An error occurred while processing your request.", error_details=traceback.format_exc(), - site_config=CONFIG, - SOFTWARE_RELEASE=SOFTWARE_RELEASE, ) return web.Response(text=rendered, status=500, content_type="text/html")