mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-08-12 11:53:18 +02:00
Merge pull request #9 from madeofstown/pablo-merge
Restoring what was lost
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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: <a href="/nodelist">Nodes</a> - <a href="/chat">Conversations</a> - <a href="/firehose">See <strong>everything</strong> </a>
|
||||
- Mesh Graph <a href="/nodegraph/LongFast">LF</a> - <a href="/nodegraph/MediumSlow">MS </a> - <a href="/stats">Stats </a>
|
||||
- <a href="/net">Weekly Net</a> - <a href="/map">Map</a> - <a href="/top">Top Traffic</a></div><br>
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user