Merge pull request #9 from madeofstown/pablo-merge

Restoring what was lost
This commit is contained in:
Pablo Revilla
2025-03-08 09:58:51 -08:00
committed by GitHub
11 changed files with 116 additions and 67 deletions
+7 -1
View File
@@ -46,9 +46,14 @@ port = 8081
tls_cert =
acme_challenge =
[site]
domain = http://meshview.bayme.sh
title = Bay Area Mesh
message =
[mqtt]
server = mqtt.bayme.sh
topics = ["msh/US/bayarea/#", "msh/US/CA/mrymesh/#"]
topics = ["msh/US/bayarea/#", "msh/US/CA/mrymesh/#"]
port = 1883
username = meshdev
password = large4cats
@@ -72,5 +77,6 @@ Now you can hit http://localhost:8081/ ***(if you did not change the web server
You can specify the path to your `config.ini` file with the run command argument `--config`
``` bash
./env/bin/python startdb.py --config /path/to/config.ini
./env/bin/python main.py --config /path/to/config.ini
```
+4 -10
View File
@@ -5,7 +5,7 @@ from meshview import mqtt_reader
from meshview import database
from meshview import mqtt_store
from meshview import web
import json
async def load_database_from_mqtt(mqtt_server: str , mqtt_port: int, topic: list, mqtt_user: str | None = None, mqtt_passwd: str | None = None):
@@ -16,15 +16,8 @@ async def load_database_from_mqtt(mqtt_server: str , mqtt_port: int, topic: list
async def main(config):
database.init_database(config["database"]["connection_string"])
await database.create_tables()
mqtt_user = None
mqtt_passwd = None
if config["mqtt"]["username"] != "":
mqtt_user: str = config["mqtt"]["username"]
if config["mqtt"]["password"] != "":
mqtt_passwd: str = config["mqtt"]["password"]
mqtt_topics = json.loads(config["mqtt"]["topics"])
#await database.create_tables()
async with asyncio.TaskGroup() as tg:
tg.create_task(
web.run_server(
@@ -44,6 +37,7 @@ def load_config(file_path):
return config
if __name__ == '__main__':
parser = argparse.ArgumentParser("meshview")
parser.add_argument("--config", help="Path to the configuration file.", default="config.ini")
+7
View File
@@ -68,3 +68,10 @@ class Traceroute(Base):
done: Mapped[bool] = mapped_column(nullable=True)
route: Mapped[bytes] = mapped_column(nullable=True)
import_time: Mapped[datetime] = mapped_column(nullable=True)
class SiteConfig(Base):
__tablename__="site_config"
id: Mapped[int] = mapped_column(primary_key=True)
site_domain: Mapped[str] = mapped_column(nullable=False)
site_title: Mapped[str] = mapped_column(nullable=False)
site_message: Mapped[str] = mapped_column(nullable=True)
+9 -2
View File
@@ -2,9 +2,10 @@ import datetime
from sqlalchemy import select, func
from sqlalchemy.orm import lazyload
from meshview import database
from meshview.models import Packet, PacketSeen, Node, Traceroute
from meshview.models import Packet, PacketSeen, Node, Traceroute, SiteConfig
from sqlalchemy import text
async def get_node(node_id):
async with database.async_session() as session:
result = await session.execute(select(Node).where(Node.node_id == node_id))
@@ -313,4 +314,10 @@ async def get_nodes(role=None, channel=None, hw_model=None):
return [] # Return an empty list in case of failure
async def get_site_config():
async with database.async_session() as session:
query = select(SiteConfig)
result = await session.execute(query)
#print(result.scalar())
site_config = result.scalars().all()[-1]
return site_config
+2 -1
View File
@@ -35,7 +35,8 @@
</style>
</head>
<body hx-indicator="#spinner">
<br><div style="text-align:center"><strong>Bay Area Mesh - http://bayme.sh</strong></div>
<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>
+4 -4
View File
@@ -1,6 +1,6 @@
<div class="row chat-packet">
<span class="col-2 timestamp">{{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}} </span>
<span class="col-1 timestamp"><a href="/packet/{{packet.id}}">✉️</a> {{packet.from_node.channel}}</span>
<span class="col-2 username"><a href="/packet_list/{{packet.from_node_id}}">{{packet.from_node.long_name or (packet.from_node_id | node_id_to_hex) }}</a></span>
<span class="col-6 message">{{packet.payload}}</span>
<span class="col-1 timestamp" style="font-size: smaller;"><a href="/packet/{{packet.id}}">✉️</a> {{packet.from_node.channel}}<div>{{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}</div></span>
<!-- <span class="col-1 timestamp"></span> -->
<span class="col-4 username" style="text-align: center;"><a href="/packet_list/{{packet.from_node_id}}">{{packet.from_node.long_name or (packet.from_node_id | node_id_to_hex) }}</a></span>
<span class="col message" style="text-align: right;">{{packet.payload}}</span>
</div>
+45 -17
View File
@@ -41,18 +41,28 @@
<!-- Node Information Card -->
<div class="card" id="node_info">
{% if node %}
<div class="card-header">
{{node.long_name}} ({{node.node_id|node_id_to_hex}})
</div>
<div class="card-body">
<dl>
<dt>ShortName</dt>
<dd>{{node.short_name}}</dd>
<dt>HW Model</dt>
<dd>{{node.hw_model}}</dd>
<dt>Role</dt>
<dd>{{node.role}}</dd>
</dl>
<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>
@@ -63,11 +73,6 @@
{% endif %}
</div>
</div>
<div class="col mb-3">
<!-- Map Container -->
<div id="map"></div>
</div>
</div>
<div class="row">
@@ -85,6 +90,29 @@
</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
+1 -2
View File
@@ -3,11 +3,10 @@
{% block body %}
<div class="container">
<div class="row">
<div class="col-6">
<div>
{% include 'packet.html' %}
</div>
<div
class="col-6 sticky-top"
id="packet_details"
hx-get="/packet_details/{{packet.id}}"
hx-trigger="load"
+16 -25
View File
@@ -32,7 +32,6 @@ with open(os.path.join(os.path.dirname(__file__), '1x1.png'), 'rb') as png:
empty_png = png.read()
@dataclass
class Packet:
id: int
@@ -109,23 +108,6 @@ class UplinkedNode:
rssi: float
def generate_responce(request, body, raw_node_id="", node=None):
if "HX-Request" in request.headers:
return web.Response(text=body, content_type="text/html")
template = env.get_template("index.html")
return web.Response(
text=template.render(
is_hx_request="HX-Request" in request.headers,
raw_node_id=raw_node_id,
node_html=Markup(body),
node=node,
),
content_type="text/html",
)
async def build_trace(node_id):
trace = []
for raw_p in await store.get_packets_from(node_id, PortNum.POSITION_APP, since=datetime.timedelta(hours=24)):
@@ -268,6 +250,8 @@ async def node_match(request):
return web.Response(
text=template.render(
node_options=node_options,
site_config = await store.get_site_config()
),
content_type="text/html",
)
@@ -281,6 +265,7 @@ async def packet_list(request):
else:
portnum = None
async with asyncio.TaskGroup() as tg:
node = tg.create_task(store.get_node(node_id))
raw_packets = tg.create_task(store.get_packets(node_id,portnum, limit=200))
@@ -301,6 +286,7 @@ async def packet_list(request):
neighbors=neighbors,
has_telemetry=await has_telemetry,
query_string=request.query_string,
site_config = await store.get_site_config(),
),
content_type="text/html",
)
@@ -356,6 +342,7 @@ async def packet_details(request):
from_node_cord=from_node_cord,
uplinked_nodes=uplinked_nodes,
node=node,
site_config = await store.get_site_config(),
),
content_type="text/html",
)
@@ -373,10 +360,12 @@ async def packet_details(request):
text=template.render(
packets=(Packet.from_model(p) for p in packets),
portnum=portnum,
site_config = await store.get_site_config(),
),
content_type="text/html",
)
@routes.get("/packet/{packet_id}")
async def packet(request):
packet = await store.get_packet(int(request.match_info["packet_id"]))
@@ -388,7 +377,7 @@ async def packet(request):
template = env.get_template("packet_index.html")
return web.Response(
text=template.render(packet=Packet.from_model(packet), node=node),
text=template.render(packet=Packet.from_model(packet), site_config = await store.get_site_config()),
content_type="text/html",
)
@@ -1086,7 +1075,7 @@ async def nodelist(request):
template = env.get_template("nodelist.html")
print_memory_usage()
return web.Response(
text=template.render(nodes=nodes),
text=template.render(nodes=nodes, site_config = await store.get_site_config()),
content_type="text/html",
)
except Exception as e:
@@ -1122,7 +1111,7 @@ async def net(request):
# Render template
template = env.get_template("net.html")
return web.Response(
text=template.render(packets=filtered_packets),
text=template.render(packets=filtered_packets, site_config = await store.get_site_config()),
content_type="text/html",
)
@@ -1145,7 +1134,7 @@ async def map(request):
template = env.get_template("map.html")
print_memory_usage()
return web.Response(
text=template.render(nodes=nodes),
text=template.render(nodes=nodes, site_config = await store.get_site_config()),
content_type="text/html",
)
except Exception as e:
@@ -1179,6 +1168,7 @@ async def stats(request):
total_packets_seen=total_packets_seen,
total_nodes_longfast=total_nodes_longfast,
total_nodes_mediumslow=total_nodes_mediumslow,
site_config = await store.get_site_config(),
),
content_type="text/html",
)
@@ -1199,12 +1189,12 @@ async def top(request):
node_traffic = await store.get_node_traffic(int(node_id))
print(node_traffic)
template = env.get_template("node_traffic.html") # Render a different template
html_content = template.render(traffic=node_traffic, node_id=node_id)
html_content = template.render(traffic=node_traffic, node_id=node_id, site_config = await store.get_site_config())
else:
# Otherwise, fetch top traffic nodes as usual
top_nodes = await store.get_top_traffic_nodes()
template = env.get_template("top.html")
html_content = template.render(nodes=top_nodes)
html_content = template.render(nodes=top_nodes, site_config = await store.get_site_config())
return web.Response(
text=html_content,
@@ -1242,7 +1232,7 @@ async def chat(request):
#print("Rendering template...")
template = env.get_template("chat.html")
return web.Response(
text=template.render(packets=filtered_packets),
text=template.render(packets=filtered_packets, site_config = await store.get_site_config()),
content_type="text/html",
)
@@ -1332,6 +1322,7 @@ async def nodegraph(request):
text=template.render(
nodes=nodes_with_edges,
edges=edges, # Pass edges with color info
site_config = await store.get_site_config(),
),
content_type="text/html",
)
+6 -4
View File
@@ -4,6 +4,11 @@ port = 8081
tls_cert =
acme_challenge =
[site]
domain = http://meshview.bayme.sh
title = Bay Area Mesh
message =
[mqtt]
server = mqtt.bayme.sh
topics = ["msh/US/bayarea/#", "msh/US/CA/mrymesh/#"]
@@ -12,7 +17,4 @@ username = meshdev
password = large4cats
[database]
connection_string = sqlite+aiosqlite:///packets.db
[website]
title = San Francisco Bay Area Mesh
connection_string = sqlite+aiosqlite:///packets.db
+15 -1
View File
@@ -4,6 +4,7 @@ import configparser
from meshview import mqtt_reader
from meshview import mqtt_database
from meshview import mqtt_store
from meshview import models
import json
@@ -23,12 +24,25 @@ async def main(config):
if config["mqtt"]["password"] != "":
mqtt_passwd: str = config["mqtt"]["password"]
mqtt_topics = json.loads(config["mqtt"]["topics"])
# Create database with site configuration
async with mqtt_database.async_session() as session:
print(config["site"]["domain"])
site_config = models.SiteConfig(
site_domain = config["site"]["domain"],
site_title = config["site"]["title"],
site_message = config["site"]["message"]
)
session.add(site_config)
await session.commit()
# print("Site configuration loaded to database")
async with asyncio.TaskGroup() as tg:
tg.create_task(
load_database_from_mqtt(config["mqtt"]["server"], int(config["mqtt"]["port"]), mqtt_topics, mqtt_user, mqtt_passwd)
)
def load_config(file_path):
"""Load configuration from an INI-style text file."""