diff --git a/alembic/versions/23dad03d2e42_add_is_mqtt_gateway_to_node.py b/alembic/versions/23dad03d2e42_add_is_mqtt_gateway_to_node.py new file mode 100644 index 0000000..749b462 --- /dev/null +++ b/alembic/versions/23dad03d2e42_add_is_mqtt_gateway_to_node.py @@ -0,0 +1,27 @@ +"""Add is_mqtt_gateway to node + +Revision ID: 23dad03d2e42 +Revises: a0c9c13e118f +Create Date: 2026-02-13 00:00:00.000000 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "23dad03d2e42" +down_revision: str | None = "a0c9c13e118f" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + op.add_column("node", sa.Column("is_mqtt_gateway", sa.Boolean(), nullable=True)) + + +def downgrade() -> None: + op.drop_column("node", "is_mqtt_gateway") diff --git a/meshview/lang/en.json b/meshview/lang/en.json index ed1ec18..aee43bb 100644 --- a/meshview/lang/en.json +++ b/meshview/lang/en.json @@ -80,8 +80,11 @@ "last_lat": "Last Latitude", "last_long": "Last Longitude", "channel": "Channel", + "mqtt_gateway": "MQTT", "last_seen": "Last Seen", "favorite": "Favorite", + "yes": "Yes", + "no": "No", "time_just_now": "just now", "time_min_ago": "min ago", @@ -96,8 +99,9 @@ "view_packet_details": "More details" }, - "map": { + "map": { "show_routers_only": "Show Routers Only", + "show_mqtt_only": "Show MQTT Gateways Only", "share_view": "Share This View", "reset_filters": "Reset Filters To Defaults", "unmapped_packets_title": "Unmapped Packets", @@ -105,8 +109,11 @@ "channel_label": "Channel:", "model_label": "Model:", "role_label": "Role:", + "mqtt_gateway": "MQTT Gateway:", "last_seen": "Last seen:", "firmware": "Firmware:", + "yes": "Yes", + "no": "No", "link_copied": "Link Copied!", "legend_traceroute": "Traceroute (with arrows)", "legend_neighbor": "Neighbor" @@ -192,6 +199,7 @@ "hw_model": "Hardware Model", "firmware": "Firmware", "role": "Role", + "mqtt_gateway": "MQTT Gateway", "channel": "Channel", "latitude": "Latitude", "longitude": "Longitude", @@ -214,6 +222,8 @@ "last_24h": "24h", "packets_sent": "Packets sent", "times_seen": "Times seen", + "yes": "Yes", + "no": "No", "copy_import_url": "Copy Import URL", "show_qr_code": "Show QR Code", "toggle_coverage": "Predicted Coverage", diff --git a/meshview/lang/es.json b/meshview/lang/es.json index ee7371f..a99d309 100644 --- a/meshview/lang/es.json +++ b/meshview/lang/es.json @@ -78,8 +78,11 @@ "last_lat": "Última latitud", "last_long": "Última longitud", "channel": "Canal", + "mqtt_gateway": "MQTT", "last_seen": "Última vez visto", "favorite": "Favorito", + "yes": "Sí", + "no": "No", "time_just_now": "justo ahora", "time_min_ago": "min atrás", "time_hr_ago": "h atrás", @@ -96,6 +99,7 @@ "map": { "filter_routers_only": "Mostrar solo enrutadores", "show_routers_only": "Mostrar solo enrutadores", + "show_mqtt_only": "Mostrar solo gateways MQTT", "share_view": "Compartir esta vista", "reset_filters": "Restablecer filtros", "unmapped_packets_title": "Paquetes sin mapa", @@ -103,8 +107,11 @@ "channel_label": "Canal:", "model_label": "Modelo:", "role_label": "Rol:", + "mqtt_gateway": "Gateway MQTT:", "last_seen": "Visto por última vez:", "firmware": "Firmware:", + "yes": "Sí", + "no": "No", "link_copied": "¡Enlace copiado!", "legend_traceroute": "Ruta de traceroute (flechas de dirección)", "legend_neighbor": "Vínculo de vecinos" @@ -178,6 +185,7 @@ "hw_model": "Modelo de Hardware", "firmware": "Firmware", "role": "Rol", + "mqtt_gateway": "Gateway MQTT", "channel": "Canal", "latitude": "Latitud", "longitude": "Longitud", @@ -200,6 +208,8 @@ "last_24h": "24h", "packets_sent": "Paquetes enviados", "times_seen": "Veces visto", + "yes": "Sí", + "no": "No", "copy_import_url": "Copiar URL de importación", "show_qr_code": "Mostrar código QR", "toggle_coverage": "Cobertura predicha", diff --git a/meshview/models.py b/meshview/models.py index 05765a6..7302eca 100644 --- a/meshview/models.py +++ b/meshview/models.py @@ -20,6 +20,7 @@ class Node(Base): last_lat: Mapped[int] = mapped_column(BigInteger, nullable=True) last_long: Mapped[int] = mapped_column(BigInteger, nullable=True) channel: Mapped[str] = mapped_column(nullable=True) + is_mqtt_gateway: Mapped[bool] = mapped_column(nullable=True) first_seen_us: Mapped[int] = mapped_column(BigInteger, nullable=True) last_seen_us: Mapped[int] = mapped_column(BigInteger, nullable=True) diff --git a/meshview/mqtt_store.py b/meshview/mqtt_store.py index a69899b..7ec7ad2 100644 --- a/meshview/mqtt_store.py +++ b/meshview/mqtt_store.py @@ -2,7 +2,7 @@ import logging import re import time -from sqlalchemy import select +from sqlalchemy import select, update from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlalchemy.dialects.sqlite import insert as sqlite_insert from sqlalchemy.exc import IntegrityError @@ -15,6 +15,8 @@ from meshview.models import Node, NodePublicKey, Packet, PacketSeen, Traceroute logger = logging.getLogger(__name__) +MQTT_GATEWAY_CACHE: set[int] = set() + async def process_envelope(topic, env): # MAP_REPORT_APP @@ -131,6 +133,12 @@ async def process_envelope(topic, env): else: node_id = int(env.gateway_id[1:], 16) + if node_id not in MQTT_GATEWAY_CACHE: + MQTT_GATEWAY_CACHE.add(node_id) + await session.execute( + update(Node).where(Node.node_id == node_id).values(is_mqtt_gateway=True) + ) + result = await session.execute( select(PacketSeen).where( PacketSeen.packet_id == env.packet.id, @@ -266,3 +274,11 @@ async def process_envelope(topic, env): ) await session.commit() + + +async def load_gateway_cache(): + async with mqtt_database.async_session() as session: + result = await session.execute( + select(Node.node_id).where(Node.is_mqtt_gateway == True) # noqa: E712 + ) + MQTT_GATEWAY_CACHE.update(result.scalars().all()) diff --git a/meshview/templates/map.html b/meshview/templates/map.html index e55d05b..f9832af 100644 --- a/meshview/templates/map.html +++ b/meshview/templates/map.html @@ -110,6 +110,8 @@