Add pager message filtering (closes #40)

Add ability to filter out unwanted pager messages from display:
- Hide "Tone Only" messages by default (toggle in UI)
- Custom keyword filter (comma-separated list)
- Filtered messages are still logged and counted, just hidden from view
- Filter settings persist in localStorage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-01-07 20:14:46 +00:00
parent 45b35ea5b0
commit 27dd868d97
+91 -9
View File
@@ -403,6 +403,23 @@
</div> </div>
</div> </div>
<div class="section">
<h3>Message Filters</h3>
<div class="checkbox-group" style="margin-bottom: 10px;">
<label>
<input type="checkbox" id="filterToneOnly" checked onchange="savePagerFilters()">
Hide "Tone Only" messages
</label>
</div>
<div class="form-group">
<label>Hide messages containing (comma-separated)</label>
<input type="text" id="filterKeywords" placeholder="e.g. test, spam, alert" onchange="savePagerFilters()">
</div>
<div class="info-text" style="font-size: 10px; color: #666; margin-top: 5px;">
Messages matching these keywords will be hidden from display but still logged.
</div>
</div>
<button class="run-btn" id="startBtn" onclick="startDecoding()"> <button class="run-btn" id="startBtn" onclick="startDecoding()">
Start Decoding Start Decoding
</button> </button>
@@ -1711,8 +1728,15 @@
let pocsagCount = 0; let pocsagCount = 0;
let flexCount = 0; let flexCount = 0;
let sensorCount = 0; let sensorCount = 0;
let filteredCount = 0; // Count of filtered messages
let deviceList = {{ devices | tojson | safe }}; let deviceList = {{ devices | tojson | safe }};
// Pager message filter settings
let pagerFilters = {
hideToneOnly: true,
keywords: []
};
// Aircraft (ADS-B) state // Aircraft (ADS-B) state
let adsbAircraft = {}; let adsbAircraft = {};
let adsbMsgCount = 0; let adsbMsgCount = 0;
@@ -1732,6 +1756,50 @@
setInterval(updateHeaderClock, 1000); setInterval(updateHeaderClock, 1000);
updateHeaderClock(); // Initial call updateHeaderClock(); // Initial call
// Pager message filter functions
function loadPagerFilters() {
const saved = localStorage.getItem('pagerFilters');
if (saved) {
try {
pagerFilters = JSON.parse(saved);
} catch (e) {
console.warn('Failed to load pager filters:', e);
}
}
// Update UI
document.getElementById('filterToneOnly').checked = pagerFilters.hideToneOnly;
document.getElementById('filterKeywords').value = pagerFilters.keywords.join(', ');
}
function savePagerFilters() {
pagerFilters.hideToneOnly = document.getElementById('filterToneOnly').checked;
const keywordsInput = document.getElementById('filterKeywords').value;
pagerFilters.keywords = keywordsInput
.split(',')
.map(k => k.trim().toLowerCase())
.filter(k => k.length > 0);
localStorage.setItem('pagerFilters', JSON.stringify(pagerFilters));
}
function shouldFilterMessage(msg) {
// Check for Tone Only filter
if (pagerFilters.hideToneOnly) {
if (msg.message === '[Tone Only]' || msg.msg_type === 'Tone') {
return true;
}
}
// Check keyword filters
if (pagerFilters.keywords.length > 0) {
const msgLower = (msg.message || '').toLowerCase();
for (const keyword of pagerFilters.keywords) {
if (msgLower.includes(keyword)) {
return true;
}
}
}
return false;
}
// Sync header stats with output panel stats // Sync header stats with output panel stats
function syncHeaderStats() { function syncHeaderStats() {
// Pager stats // Pager stats
@@ -2136,6 +2204,9 @@
// Auto-connect to gpsd if available // Auto-connect to gpsd if available
autoConnectGps(); autoConnectGps();
// Load pager message filters
loadPagerFilters();
}); });
// Toggle section collapse // Toggle section collapse
@@ -3092,18 +3163,13 @@
placeholder.remove(); placeholder.remove();
} }
// Store message for export // Store message for export (always, even if filtered)
allMessages.push(msg); allMessages.push(msg);
// Play audio alert // Check if message should be filtered from display
playAlert(); const isFiltered = shouldFilterMessage(msg);
// Update signal meter
pulseSignal();
// Add to waterfall
addWaterfallPoint(Date.now(), 0.8);
// Update counts (always, even if filtered)
msgCount++; msgCount++;
document.getElementById('msgCount').textContent = msgCount; document.getElementById('msgCount').textContent = msgCount;
@@ -3118,6 +3184,21 @@
document.getElementById('flexCount').textContent = flexCount; document.getElementById('flexCount').textContent = flexCount;
} }
// If filtered, skip display but update filtered count
if (isFiltered) {
filteredCount++;
return;
}
// Play audio alert (only for non-filtered messages)
playAlert();
// Update signal meter
pulseSignal();
// Add to waterfall
addWaterfallPoint(Date.now(), 0.8);
const isNumeric = /^[0-9\s\-\*\#U]+$/.test(msg.message); const isNumeric = /^[0-9\s\-\*\#U]+$/.test(msg.message);
const relativeTime = getRelativeTime(msg.timestamp); const relativeTime = getRelativeTime(msg.timestamp);
@@ -3216,6 +3297,7 @@
pocsagCount = 0; pocsagCount = 0;
flexCount = 0; flexCount = 0;
sensorCount = 0; sensorCount = 0;
filteredCount = 0;
allMessages = []; allMessages = [];
uniqueDevices.clear(); uniqueDevices.clear();
document.getElementById('msgCount').textContent = '0'; document.getElementById('msgCount').textContent = '0';