diff --git a/src/meshcore_hub/web/routes/map.py b/src/meshcore_hub/web/routes/map.py index c5f982e..b93691c 100644 --- a/src/meshcore_hub/web/routes/map.py +++ b/src/meshcore_hub/web/routes/map.py @@ -1,6 +1,7 @@ """Map page route.""" import logging +from typing import Any from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse, JSONResponse @@ -23,10 +24,38 @@ async def map_page(request: Request) -> HTMLResponse: @router.get("/map/data") async def map_data(request: Request) -> JSONResponse: - """Return node location data as JSON for the map.""" - nodes_with_location = [] + """Return node location data as JSON for the map. + + Includes role tag, member ownership info, and all data needed for filtering. + """ + nodes_with_location: list[dict[str, Any]] = [] + members_list: list[dict[str, Any]] = [] + members_by_key: dict[str, dict[str, Any]] = {} + error: str | None = None + total_nodes = 0 + nodes_with_coords = 0 try: + # Fetch all members to build lookup by public_key + members_response = await request.app.state.http_client.get( + "/api/v1/members", params={"limit": 500} + ) + if members_response.status_code == 200: + members_data = members_response.json() + for member in members_data.get("items", []): + member_info = { + "id": member.get("id"), + "name": member.get("name"), + "callsign": member.get("callsign"), + } + members_list.append(member_info) + if member.get("public_key"): + members_by_key[member["public_key"]] = member_info + else: + logger.warning( + f"Failed to fetch members: status {members_response.status_code}" + ) + # Fetch all nodes from API response = await request.app.state.http_client.get( "/api/v1/nodes", params={"limit": 500} @@ -34,6 +63,7 @@ async def map_data(request: Request) -> JSONResponse: if response.status_code == 200: data = response.json() nodes = data.get("items", []) + total_nodes = len(nodes) # Filter nodes with location tags for node in nodes: @@ -41,6 +71,8 @@ async def map_data(request: Request) -> JSONResponse: lat = None lon = None friendly_name = None + role = None + for tag in tags: key = tag.get("key") if key == "lat": @@ -55,37 +87,62 @@ async def map_data(request: Request) -> JSONResponse: pass elif key == "friendly_name": friendly_name = tag.get("value") + elif key == "role": + role = tag.get("value") if lat is not None and lon is not None: + nodes_with_coords += 1 # Use friendly_name, then node name, then public key prefix display_name = ( friendly_name or node.get("name") or node.get("public_key", "")[:12] ) + public_key = node.get("public_key") + + # Find owner member if exists + owner = members_by_key.get(public_key) + nodes_with_location.append( { - "public_key": node.get("public_key"), + "public_key": public_key, "name": display_name, "adv_type": node.get("adv_type"), "lat": lat, "lon": lon, "last_seen": node.get("last_seen"), + "role": role, + "is_infra": role == "infra", + "owner": owner, } ) + else: + error = f"API returned status {response.status_code}" + logger.warning(f"Failed to fetch nodes: {error}") except Exception as e: + error = str(e) logger.warning(f"Failed to fetch nodes for map: {e}") # Get network center location network_location = request.app.state.network_location + logger.info( + f"Map data: {total_nodes} total nodes, " f"{nodes_with_coords} with coordinates" + ) + return JSONResponse( { "nodes": nodes_with_location, + "members": members_list, "center": { "lat": network_location[0], "lon": network_location[1], }, + "debug": { + "total_nodes": total_nodes, + "nodes_with_coords": nodes_with_coords, + "error": error, + }, } ) diff --git a/src/meshcore_hub/web/templates/map.html b/src/meshcore_hub/web/templates/map.html index 0480b14..994df55 100644 --- a/src/meshcore_hub/web/templates/map.html +++ b/src/meshcore_hub/web/templates/map.html @@ -5,7 +5,7 @@ {% block extra_head %}