Added new searching capabilities to our node reports

This commit is contained in:
Pablo Revilla
2025-02-11 23:01:15 -08:00
parent 94d803df66
commit 9fbe3400ef
4 changed files with 126 additions and 27 deletions
-2
View File
@@ -28,5 +28,3 @@ Other Options:
--topic
MQTT Topic, default is 'msh/US/bayarea/#'
<img src="images/main.png" alt="Main Page" >
+1 -2
View File
@@ -524,7 +524,7 @@ async def get_nodes(role=None, channel=None, hw_model=None):
"""
try:
async with database.async_session() as session:
print(channel) # Debugging output (consider replacing with logging)
#print(channel) # Debugging output (consider replacing with logging)
# Start with a base query selecting all nodes
query = select(Node)
@@ -546,7 +546,6 @@ async def get_nodes(role=None, channel=None, hw_model=None):
# Execute the query and retrieve results
result = await session.execute(query)
nodes = result.scalars().all()
return nodes # Return the list of nodes
except Exception as e:
+123 -21
View File
@@ -16,6 +16,7 @@ th, td {
th {
background-color: #1f1f1f;
color: white;
cursor: pointer;
}
tr:nth-child(even) {
@@ -25,37 +26,86 @@ tr:nth-child(even) {
tr:nth-child(odd) {
background-color: #222;
}
.search-container {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.search, .filter-role, .filter-channel, .export-btn {
padding: 8px;
border: 1px solid #333;
border-radius: 4px;
}
.filter-role, .filter-channel {
cursor: pointer;
}
.export-btn {
background: #28a745;
color: white;
border: none;
cursor: pointer;
}
.export-btn:hover {
background: #218838;
}
{% endblock %}
{% block body %}
<div>
<div id="node-list">
<div class="search-container">
<input class="search" placeholder="Search nodes..." />
<!-- Filter by Role Dropdown -->
<select class="filter-role" onchange="applyFilters()">
<option value="">Filter by Role</option>
{% for node in nodes|groupby('role') %}
<option value="{{ node.grouper }}">{{ node.grouper }}</option>
{% endfor %}
</select>
<!-- Filter by Channel Dropdown -->
<select class="filter-channel" onchange="applyFilters()">
<option value="">Filter by Channel</option>
{% for node in nodes|groupby('channel') %}
<option value="{{ node.grouper }}">{{ node.grouper }}</option>
{% endfor %}
</select>
<button class="export-btn" onclick="exportToCSV()">Export to CSV</button>
</div>
{% if nodes %}
<table>
<table id="node-table">
<thead>
<tr>
<th>Long Name</th>
<th>Short Name</th>
<th>HW Model</th>
<th>Firmware</th>
<th>Role</th>
<th>Last Latitude</th>
<th>Last Longitude</th>
<th>Channel</th>
<th>Last Update</th>
<th class="sort" data-sort="long_name">Long Name</th>
<th class="sort" data-sort="short_name">Short Name</th>
<th class="sort" data-sort="hw_model">HW Model</th>
<th class="sort" data-sort="firmware">Firmware</th>
<th class="sort" data-sort="role">Role</th>
<th class="sort" data-sort="last_lat">Last Latitude</th>
<th class="sort" data-sort="last_long">Last Longitude</th>
<th class="sort" data-sort="channel">Channel</th>
<th class="sort" data-sort="last_update">Last Update</th>
</tr>
</thead>
<tbody>
<tbody class="list">
{% for node in nodes %}
<tr>
<td> <a href="/packet_list/{{node.node_id }}">{{node.long_name}}</a></td>
<td>{{ node.short_name }}</td>
<td><a href="/nodelist?hw_model={{node.hw_model}}">{{node.hw_model if node.hw_model else "N/A"}}</a></td>
<td>{{ node.firmware }}</td>
<td><a href="/nodelist?role={{node.role}}">{{node.role if node.role else "N/A"}}</a></td>
<td>{{ "{:.7f}".format(node.last_lat / 10**7) if node.last_lat else "N/A" }}</td>
<td>{{ "{:.7f}".format(node.last_long / 10**7) if node.last_long else "N/A" }}</td>
<td><a href="/nodelist?channel={{node.channel}}">{{node.channel if node.channel else "N/A"}}</a></td>
<td>{{ node.last_update.strftime('%-I:%M:%S %p - %m-%d-%Y') if node.last_update else "N/A" }}</td>
<td class="long_name"> <a href="/packet_list/{{ node.node_id }}">{{ node.long_name }}</a></td>
<td class="short_name">{{ node.short_name }}</td>
<td class="hw_model">{{ node.hw_model if node.hw_model else "N/A" }}</td>
<td class="firmware">{{ node.firmware }}</td>
<td class="role">{{ node.role if node.role else "N/A" }}</td>
<td class="last_lat">{{ "{:.7f}".format(node.last_lat / 10**7) if node.last_lat else "N/A" }}</td>
<td class="last_long">{{ "{:.7f}".format(node.last_long / 10**7) if node.last_long else "N/A" }}</td>
<td class="channel">{{ node.channel if node.channel else "N/A" }}</td>
<td class="last_update">{{ node.last_update.strftime('%-I:%M:%S %p - %m-%d-%Y') if node.last_update else "N/A" }}</td>
</tr>
{% endfor %}
</tbody>
@@ -64,4 +114,56 @@ tr:nth-child(odd) {
<p>No nodes found.</p>
{% endif %}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/list.js/2.3.1/list.min.js"></script>
<script>
var nodeList;
document.addEventListener("DOMContentLoaded", function () {
var options = {
valueNames: ["long_name", "short_name", "hw_model", "firmware", "role", "last_lat", "last_long", "channel", "last_update"]
};
nodeList = new List("node-list", options);
});
function applyFilters() {
var selectedRole = document.querySelector(".filter-role").value;
var selectedChannel = document.querySelector(".filter-channel").value;
nodeList.filter(function (item) {
var matchesRole = selectedRole === "" || item.values().role === selectedRole;
var matchesChannel = selectedChannel === "" || item.values().channel === selectedChannel;
return matchesRole && matchesChannel;
});
}
function exportToCSV() {
var table = document.getElementById("node-table");
var rows = table.querySelectorAll("tr");
var csvContent = [];
// Extract header row
var headers = [];
table.querySelectorAll("th").forEach(th => headers.push(th.innerText));
csvContent.push(headers.join(","));
// Extract table rows
rows.forEach(row => {
var cells = row.querySelectorAll("td");
if (cells.length > 0) {
var rowData = [];
cells.forEach(cell => rowData.push(cell.innerText));
csvContent.push(rowData.join(","));
}
});
// Create CSV file and trigger download
var csvString = csvContent.join("\n");
var blob = new Blob([csvString], { type: "text/csv" });
var a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "nodes_list.csv";
a.click();
}
</script>
{% endblock %}
+2 -2
View File
@@ -1477,9 +1477,9 @@ async def nodelist(request):
role = request.query.get("role")
#print(role)
channel = request.query.get("channel")
print(channel)
#print(channel)
hw_model = request.query.get("hw_model")
print(hw_model)
#print(hw_model)
nodes= await store.get_nodes(role,channel, hw_model)
template = env.get_template("nodelist.html")
return web.Response(