Fixed Date format to mm/dd/YYYY.

Added /api that dumps a json with all the nodes.
This commit is contained in:
Pablo Revilla
2025-03-14 10:27:45 -07:00
parent da8490f3b4
commit 4ef3c27937
11 changed files with 141 additions and 45 deletions
+8
View File
@@ -23,6 +23,14 @@ class Node(Base):
channel: Mapped[str] = mapped_column(nullable=True)
last_update: Mapped[datetime] = mapped_column(nullable=True)
def to_dict(self):
"""Convert SQLAlchemy object to a dictionary, excluding last_update."""
return {
column.name: getattr(self, column.name)
for column in self.__table__.columns
if column.name != "last_update" # Exclude last_update
}
class Packet(Base):
__tablename__ = "packet"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
+1 -1
View File
@@ -1,5 +1,5 @@
<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-2 timestamp">{{packet.import_time.strftime('%-I:%M:%S %p - %m-%d-%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>
+9 -1
View File
@@ -1,4 +1,12 @@
{% extends "base.html" %}
{% block css %}
/* Set the maximum width of the page to 900px */
.container {
max-width: 900px;
margin: 0 auto; /* Center the content horizontally */
}
{% endblock %}
{% block body %}
@@ -82,7 +90,7 @@
<option value="{{ value }}" {% if value == portnum %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
<button type="button" id="auto-refresh-button" class="col-2 m-2 btn btn-primary">Enable Auto-Refresh</button>
<button type="button" id="auto-refresh-button">Enable Auto-Refresh</button>
</form>
<div class="row">
<div class="col-xs" id="packet_list">
+1 -1
View File
@@ -1,5 +1,5 @@
<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-2 timestamp">{{packet.import_time.strftime('%-I:%M:%S %p - %m-%d-%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>
+8 -8
View File
@@ -23,6 +23,12 @@
div.tab-pane > dl {
display: inline-block;
}
/* Set the maximum width of the page to 900px */
.container {
max-width: 900px;
margin: 0 auto; /* Center the content horizontally */
}
{% endblock %}
{% block body %}
@@ -30,12 +36,7 @@
{% include "search_form.html" %}
<div id="node" class="container text-center">
<div class="container"
{% if node %}
hx-ext="sse"
sse-connect="/events?node_id={{node_id}}{% if portnum %}&portnum={{portnum}}{% endif %}"
{% endif %}
>
<div class="container">
<div class="row">
<div class="col mb-3">
<!-- Node Information Card -->
@@ -106,7 +107,7 @@
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
@@ -230,7 +231,6 @@
</script>
{% endif %}
{% endblock %}
+8 -6
View File
@@ -49,8 +49,14 @@
{% endblock %}
{% block body %}
<section>
<h2 class="table-title">{{ traffic[0].long_name }} (last 24 hours)</h2>
<section>
<h2 class="table-title">
{% if traffic %}
{{ traffic[0].long_name }} (last 24 hours)
{% else %}
No Traffic Data Available
{% endif %}
</h2>
<table class="traffic-table">
<thead>
<tr>
@@ -80,10 +86,6 @@
MAP_REPORT_APP
{% elif port.portnum == 0 %}
UNKNOWN_APP
{% elif port.portnum == 0 %}
UNKNOWN_APP
{% elif port.portnum == 0 %}
UNKNOWN_APP
{% else %}
{{ port.portnum }}
{% endif %}
+1 -1
View File
@@ -33,7 +33,7 @@
<div class="card-text text-start">
<dl>
<dt>Import Time</dt>
<dd>{{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}</dd>
<dd>{{packet.import_time.strftime('%-I:%M:%S %p - %m-%d-%Y')}}</dd>
<dt>packet</dt>
<dd><pre>{{packet.data}}</pre></dd>
<dt>payload</dt>
+3 -3
View File
@@ -13,7 +13,7 @@
<div class="card-text text-start">
<dl>
<dt>Import Time</dt>
<dd>{{seen.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}</dd>
<dd>{{seen.import_time.strftime('%-I:%M:%S %p - %m-%d-%Y')}}</dd>
<dt>rx_time</dt>
<dd>{{seen.rx_time|format_timestamp}}</dd>
<dt>hop_limit</dt>
@@ -36,7 +36,7 @@
{% if map_center %}
<script>
var details_map = L.map('details_map').setView({{ map_center | tojson }}, 12);
var details_map = L.map('details_map').setView({{ map_center | tojson }}, 10);
var markers = L.featureGroup();
markers.addTo(details_map);
@@ -102,7 +102,7 @@
// Ensure markers are added before adjusting map bounds
setTimeout(() => {
if (markers.getLayers().length > 0) {
details_map.fitBounds(markers.getBounds().pad(0.1), { animate: true });
details_map.fitBounds(markers.getBounds().pad(0.3), { animate: true });
}
}, 500); // Delay to ensure markers load
+7
View File
@@ -1,5 +1,12 @@
{% extends "base.html" %}
{% block css %}
/* Set the maximum width of the page to 900px */
.container {
max-width: 900px;
margin: 0 auto; /* Center the content horizontally */
}
{% endblock %}
{% block body %}
<div class="container">
<div class="row">
+69 -23
View File
@@ -5,56 +5,102 @@
height: 95vh;
overflow: scroll;
}
/* Set the maximum width of the page to 600px */
.container {
max-width: 600px;
margin: 0 auto; /* Center the content horizontally */
}
/* Adjust styling for sections */
.card-section {
background-color: #272b2f;
border: 1px solid #474b4e;
padding: 15px;
margin-bottom: 10px;
border-radius: 10px;
}
.section-header {
font-size: 16px;
margin: 0;
font-weight: 500;
}
.section-value {
font-weight: 700;
color: #03dac6;
}
.percentage {
font-size: 12px;
color: #ffeb3b;
font-weight: 400;
}
.main-container {
max-width: 600px;
margin: auto;
text-align: center;
}
.main-header {
font-size: 22px;
margin-bottom: 20px;
font-weight: 600;
}
{% endblock %}
{% block body %}
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; color: #fff; background: linear-gradient(135deg, #6a11cb, #2575fc); padding: 25px; border-radius: 15px; max-width: 350px; margin: auto; text-align: center; box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);">
<h2 style="font-size: 22px; margin-bottom: 20px; font-weight: 600;">
Mesh Statistics
</h2>
<div class="main-container">
<h2 class="main-header">Mesh Statistics</h2>
<!-- Section for Total Nodes -->
<div style="background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 10px; margin-bottom: 15px;">
<p style="font-size: 16px; margin: 0; font-weight: 500;">
<div class="card-section">
<p class="section-header">
Total Active Nodes (Last 24 hours):
<span style="font-weight: 700; color: #ffeb3b;">{{ total_nodes }}</span>
<span class="section-value">{{ total_nodes }}</span>
</p>
</div>
<!-- Section for Total Nodes LongFast -->
<div style="background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 10px; margin-bottom: 10px;">
<p style="font-size: 14px; margin: 0; font-weight: 500;">
<div class="card-section">
<p class="section-header">
Total Active Nodes LongFast: <br>
<span style="font-weight: 700; color: #03dac6;">{{ total_nodes_longfast }}</span>
<span style="font-size: 12px; color: #ffeb3b; font-weight: 400;">
<span class="section-value">{{ total_nodes_longfast }}</span>
<span class="percentage">
({{ (total_nodes_longfast / total_nodes * 100) | round(2) }}%)
</span>
</p>
</div>
<!-- Section for Total Nodes MediumSlow -->
<div style="background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 10px; margin-bottom: 10px;">
<p style="font-size: 14px; margin: 0; font-weight: 500;">
<div class="card-section">
<p class="section-header">
Total Active Nodes MediumSlow: <br>
<span style="font-weight: 700; color: #03dac6;">{{ total_nodes_mediumslow }}</span>
<span style="font-size: 12px; color: #ffeb3b; font-weight: 400;">
<span class="section-value">{{ total_nodes_mediumslow }}</span>
<span class="percentage">
({{ (total_nodes_mediumslow / total_nodes * 100) | round(2) }}%)
</span>
</p>
</div>
<!-- Section for Total Packets -->
<div style="background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 10px; margin-bottom: 10px;">
<p style="font-size: 14px; margin: 0; font-weight: 500;">
<div class="card-section">
<p class="section-header">
Total Packets:
<span style="font-weight: 700; color: #03dac6;">{{ total_packets }}</span>
<span class="section-value">{{ total_packets }}</span>
</p>
</div>
<!-- Section for Total MQTT Reports -->
<div style="background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 10px;">
<p style="font-size: 14px; margin: 0; font-weight: 500;">
<div class="card-section">
<p class="section-header">
Total MQTT Reports:
<span style="font-weight: 700; color: #03dac6;">{{ total_packets_seen }}</span>
<span class="section-value">{{ total_packets_seen }}</span>
</p>
</div>
</div>
{% endblock body %}
{% endblock %}
+26 -1
View File
@@ -1093,6 +1093,32 @@ async def nodelist(request):
)
import traceback
@routes.get("/api")
async def api(request):
try:
role = request.query.get("role")
channel = request.query.get("channel")
hw_model = request.query.get("hw_model")
nodes = await store.get_nodes(role, channel, hw_model)
nodes_json = [node.to_dict() for node in nodes]
return web.json_response({"nodes": nodes_json})
except Exception as e:
import traceback
print("Error in /api endpoint:", str(e))
print(traceback.format_exc())
return web.Response(
text=f"An error occurred: {str(e)}",
status=500,
content_type="text/plain",
)
@routes.get("/net")
async def net(request):
try:
@@ -1193,7 +1219,6 @@ async def top(request):
if node_id:
# If node_id is provided, fetch traffic data for the specific node
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, site_config = CONFIG)
else: