diff --git a/dbcleanup.log b/dbcleanup.log deleted file mode 100644 index dea5664..0000000 --- a/dbcleanup.log +++ /dev/null @@ -1,14 +0,0 @@ -2025-10-14 20:13:45,221 [INFO] Daily cleanup is disabled by configuration. -2025-10-14 20:25:47,645 [INFO] Daily cleanup is disabled by configuration. -2025-10-14 20:34:48,026 [INFO] Daily cleanup is disabled by configuration. -2025-10-14 21:11:16,069 [INFO] Daily cleanup is disabled by configuration. -2025-10-14 21:19:58,777 [INFO] Daily cleanup is disabled by configuration. -2025-10-14 21:20:29,595 [INFO] Daily cleanup is disabled by configuration. -2025-10-15 10:28:37,193 [INFO] Daily cleanup is disabled by configuration. -2025-10-15 15:54:56,829 [INFO] Daily cleanup is disabled by configuration. -2025-10-15 15:59:16,304 [INFO] Daily cleanup is disabled by configuration. -2025-10-15 16:27:05,307 [INFO] Daily cleanup is disabled by configuration. -2025-10-15 16:29:14,882 [INFO] Daily cleanup is disabled by configuration. -2025-10-15 17:04:31,298 [INFO] Daily cleanup is disabled by configuration. -2025-10-15 17:11:28,215 [INFO] Daily cleanup is disabled by configuration. -2025-10-15 18:00:31,833 [INFO] Daily cleanup is disabled by configuration. diff --git a/meshview/store.py b/meshview/store.py index 8060469..39a3c37 100644 --- a/meshview/store.py +++ b/meshview/store.py @@ -384,11 +384,15 @@ async def get_packet_stats( } -async def get_channels_in_period(period_type: str = "hour", length: int = 24): +async def get_channels_in_period(period_type: str = "hour", length: int = 24, min_packets: int = 5, allowlist: list[str] | None = None): """ - Returns a list of distinct channels used in packets over a given period. + Returns a list of distinct channels used in packets over a given period, + filtered to only include channels with at least min_packets packets. + period_type: "hour" or "day" length: number of hours or days to look back + min_packets: minimum number of packets a channel must have to be included (default: 5) + allowlist: optional list of allowed channel names. If None or contains '*', all channels are allowed """ now = datetime.now() @@ -400,13 +404,23 @@ async def get_channels_in_period(period_type: str = "hour", length: int = 24): raise ValueError("period_type must be 'hour' or 'day'") async with database.async_session() as session: + # Count packets per channel and filter by minimum packet count q = ( - select(Packet.channel) + select(Packet.channel, func.count(Packet.id).label('packet_count')) .where(Packet.import_time >= start_time) - .distinct() + .where(Packet.channel.isnot(None)) + .group_by(Packet.channel) + .having(func.count(Packet.id) >= min_packets) .order_by(Packet.channel) ) result = await session.execute(q) - channels = [row[0] for row in result if row[0] is not None] + channels = [row[0] for row in result] + + # Apply allowlist filtering if specified + if allowlist and '*' not in allowlist: + # Filter to only include channels in the allowlist (case-insensitive) + allowlist_lower = [ch.lower() for ch in allowlist] + channels = [ch for ch in channels if ch.lower() in allowlist_lower] + return channels diff --git a/meshview/templates/nodegraph.html b/meshview/templates/nodegraph.html index b75b466..f885cf2 100644 --- a/meshview/templates/nodegraph.html +++ b/meshview/templates/nodegraph.html @@ -243,25 +243,23 @@ nodes.forEach(n=>{ } }); -const tracerouteChannelCounts = new Map(); +const channelCounts = new Map(); edges.forEach(edge=>{ - if(edge.type === 'traceroute'){ - const weight = typeof edge.weight === 'number' ? edge.weight : 1; - const fromChannel = channelByNodeId.get(String(edge.source)); - const toChannel = channelByNodeId.get(String(edge.target)); - if(fromChannel){ - tracerouteChannelCounts.set(fromChannel, (tracerouteChannelCounts.get(fromChannel) || 0) + weight); - } - if(toChannel){ - tracerouteChannelCounts.set(toChannel, (tracerouteChannelCounts.get(toChannel) || 0) + weight); - } + const weight = typeof edge.weight === 'number' ? edge.weight : 1; + const fromChannel = channelByNodeId.get(String(edge.source)); + const toChannel = channelByNodeId.get(String(edge.target)); + if(fromChannel){ + channelCounts.set(fromChannel, (channelCounts.get(fromChannel) || 0) + weight); + } + if(toChannel){ + channelCounts.set(toChannel, (channelCounts.get(toChannel) || 0) + weight); } }); let channelOptions = []; -if (tracerouteChannelCounts.size) { +if (channelCounts.size) { channelOptions = Array.from( - [...tracerouteChannelCounts.entries()] + [...channelCounts.entries()] .filter(([_, count]) => count > 0) .map(([channel]) => channel) ).sort(); @@ -388,10 +386,8 @@ function searchNode(){ else alert("Node not found in current channel!"); } -initializeChannelOptions().then(() => { - populateChannelDropdown(); - filterByChannel(true); -}); +populateChannelDropdown(); +filterByChannel(true); window.addEventListener('resize', ()=>chart.resize()); {% endblock %} diff --git a/meshview/web.py b/meshview/web.py index 728ab45..f7ba0eb 100644 --- a/meshview/web.py +++ b/meshview/web.py @@ -1510,9 +1510,29 @@ async def get_config(request): async def api_channels(request: web.Request): period_type = request.query.get("period_type", "hour") length = int(request.query.get("length", 24)) + + # Get min_packets from config, with fallback to query param or default + config_min_packets = CONFIG.get("site", {}).get("min_packets_for_channel", "5") + try: + default_min_packets = int(config_min_packets) + except (ValueError, TypeError): + default_min_packets = 5 + + min_packets = int(request.query.get("min_packets", default_min_packets)) + + # Get channel allowlist from config + allowlist_str = CONFIG.get("site", {}).get("channel_allowlist", "*") + if allowlist_str and allowlist_str.strip(): + # Parse comma-separated list, or use '*' for all + if allowlist_str.strip() == "*": + allowlist = None # None means all channels allowed + else: + allowlist = [ch.strip() for ch in allowlist_str.split(",") if ch.strip()] + else: + allowlist = None try: - channels = await store.get_channels_in_period(period_type, length) + channels = await store.get_channels_in_period(period_type, length, min_packets, allowlist) return web.json_response({"channels": channels}) except Exception as e: return web.json_response({"channels": [], "error": str(e)}) diff --git a/sample.config.ini b/sample.config.ini index 1931931..0d4e3ca 100644 --- a/sample.config.ini +++ b/sample.config.ini @@ -1,6 +1,8 @@ # ------------------------- # Server Configuration # ------------------------- +# important: no leading spaces on configuration lines. + [server] # The address to bind the server to. Use * to listen on all interfaces. bind = * @@ -59,6 +61,17 @@ firehose_interal=3 weekly_net_message = Weekly Mesh check-in. We will keep it open on every Wednesday from 5:00pm for checkins. The message format should be (LONG NAME) - (CITY YOU ARE IN) #BayMeshNet. net_tag = #BayMeshNet +# Channel filtering configuration +# Minimum number of packets required for a channel to appear in dropdowns +min_packets_for_channel = 5 + +# Channel allowlist: comma-separated list of channels to display, or * for all channels +# Use * to show all channels with sufficient packets +# Or specify channels like: LongFast,MediumSlow,MediumFast,ShortTurbo,LongSlow,ShortFast,ShortSlow,VLongSlow +channel_allowlist = * +# Examples of common Meshtastic channels: +#channel_allowlist = LongFast,MediumSlow,MediumFast,ShortTurbo,LongSlow,ShortFast,ShortSlow,VLongSlow + # ------------------------- # MQTT Broker Configuration # -------------------------