diff --git a/meshview/lang/en.json b/meshview/lang/en.json
index 9a3a7e8..ccfb0c9 100644
--- a/meshview/lang/en.json
+++ b/meshview/lang/en.json
@@ -181,6 +181,7 @@
"from": "From",
"to": "To",
"channel": "Channel",
+ "all_channels": "All Channels",
"port": "Port",
"links": "Links",
"unknown_app": "UNKNOWN APP",
diff --git a/meshview/lang/es.json b/meshview/lang/es.json
index fd27d02..dfe32d4 100644
--- a/meshview/lang/es.json
+++ b/meshview/lang/es.json
@@ -180,6 +180,7 @@
"from": "De",
"to": "A",
"channel": "Canal",
+ "all_channels": "Todos los canales",
"port": "Puerto",
"direct_to_mqtt": "Directo a MQTT",
"all_broadcast": "Todos"
diff --git a/meshview/templates/firehose.html b/meshview/templates/firehose.html
index d775bc9..bc5266a 100644
--- a/meshview/templates/firehose.html
+++ b/meshview/templates/firehose.html
@@ -11,6 +11,12 @@
font-size: 0.9rem;
border-radius: 6px;
}
+.firehose-controls {
+ gap: 12px;
+}
+.firehose-filter {
+ min-width: 220px;
+}
.port-tag {
display: inline-block;
@@ -89,15 +95,22 @@
{% block body %}
-
@@ -241,6 +254,7 @@ function logPacketTimes(packet) {
let lastImportTimeUs = null;
let updatesPaused = false;
let updateInterval = 3000;
+let selectedChannel = "";
async function configureFirehose() {
try {
@@ -250,6 +264,36 @@ async function configureFirehose() {
} catch {}
}
+async function loadChannels() {
+ try {
+ const res = await fetch("/api/channels");
+ if (!res.ok) return;
+
+ const data = await res.json();
+ const select = document.getElementById("channel-filter");
+ const channels = data.channels || [];
+
+ for (const channel of channels) {
+ const option = document.createElement("option");
+ option.value = channel;
+ option.textContent = channel;
+ select.appendChild(option);
+ }
+ } catch (err) {
+ console.error("Failed loading channels:", err);
+ }
+}
+
+function resetFirehose() {
+ lastImportTimeUs = null;
+ document.getElementById("packet_list").innerHTML = "";
+}
+
+function matchesSelectedChannel(packet) {
+ if (!selectedChannel) return true;
+ return (packet.channel || "").toLowerCase() === selectedChannel.toLowerCase();
+}
+
async function fetchUpdates() {
if (updatesPaused) return;
@@ -270,6 +314,8 @@ async function fetchUpdates() {
const list = document.getElementById("packet_list");
for (const pkt of packets.reverse()) {
+ if (!matchesSelectedChannel(pkt)) continue;
+
logPacketTimes(pkt);
/* FROM — includes translation */
@@ -373,6 +419,7 @@ async function fetchUpdates() {
document.addEventListener("DOMContentLoaded", async () => {
const pauseBtn = document.getElementById("pause-button");
+ const channelFilter = document.getElementById("channel-filter");
pauseBtn.addEventListener("click", () => {
updatesPaused = !updatesPaused;
@@ -383,6 +430,12 @@ document.addEventListener("DOMContentLoaded", async () => {
: (firehoseTranslations.pause || "Pause");
});
+ channelFilter.addEventListener("change", async (e) => {
+ selectedChannel = e.target.value;
+ resetFirehose();
+ await fetchUpdates();
+ });
+
document.addEventListener("click", e => {
const btn = e.target.closest(".toggle-btn");
if (!btn) return;
@@ -397,6 +450,7 @@ document.addEventListener("DOMContentLoaded", async () => {
await loadTranslationsFirehose();
await configureFirehose();
await loadNodes();
+ await loadChannels();
fetchUpdates();
setInterval(fetchUpdates, updateInterval);