diff --git a/src/meshcore_hub/web/routes/map.py b/src/meshcore_hub/web/routes/map.py index 1c7238c..c6b04a6 100644 --- a/src/meshcore_hub/web/routes/map.py +++ b/src/meshcore_hub/web/routes/map.py @@ -30,28 +30,27 @@ async def map_data(request: Request) -> JSONResponse: """ nodes_with_location: list[dict[str, Any]] = [] members_list: list[dict[str, Any]] = [] - members_by_key: dict[str, dict[str, Any]] = {} + members_by_id: 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 + # Fetch all members to build lookup by member_id 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", []): - # Only include members with public_key (required for node ownership) - if member.get("public_key"): - member_info = { - "public_key": member.get("public_key"), - "name": member.get("name"), - "callsign": member.get("callsign"), - } - members_list.append(member_info) - members_by_key[member["public_key"]] = member_info + member_info = { + "member_id": member.get("member_id"), + "name": member.get("name"), + "callsign": member.get("callsign"), + } + members_list.append(member_info) + if member.get("member_id"): + members_by_id[member["member_id"]] = member_info else: logger.warning( f"Failed to fetch members: status {members_response.status_code}" @@ -73,6 +72,7 @@ async def map_data(request: Request) -> JSONResponse: lon = None friendly_name = None role = None + node_member_id = None for tag in tags: key = tag.get("key") @@ -90,6 +90,8 @@ async def map_data(request: Request) -> JSONResponse: friendly_name = tag.get("value") elif key == "role": role = tag.get("value") + elif key == "member_id": + node_member_id = tag.get("value") if lat is not None and lon is not None: nodes_with_coords += 1 @@ -101,8 +103,10 @@ async def map_data(request: Request) -> JSONResponse: ) public_key = node.get("public_key") - # Find owner member if exists - owner = members_by_key.get(public_key) + # Find owner member by member_id tag + owner = ( + members_by_id.get(node_member_id) if node_member_id else None + ) nodes_with_location.append( { @@ -114,6 +118,7 @@ async def map_data(request: Request) -> JSONResponse: "last_seen": node.get("last_seen"), "role": role, "is_infra": role == "infra", + "member_id": node_member_id, "owner": owner, } ) diff --git a/src/meshcore_hub/web/templates/map.html b/src/meshcore_hub/web/templates/map.html index 73d733f..976291f 100644 --- a/src/meshcore_hub/web/templates/map.html +++ b/src/meshcore_hub/web/templates/map.html @@ -45,10 +45,10 @@
- +
@@ -190,16 +190,16 @@ // Core filter logic - returns filtered nodes and updates markers function applyFiltersCore() { const typeFilter = document.getElementById('filter-type').value; - const ownerFilter = document.getElementById('filter-owner').value; + const memberFilter = document.getElementById('filter-member').value; // Filter nodes const filteredNodes = allNodes.filter(node => { // Type filter (case-insensitive) if (typeFilter && normalizeType(node.adv_type) !== typeFilter) return false; - // Owner filter - if (ownerFilter) { - if (!node.owner || node.owner.public_key !== ownerFilter) return false; + // Member filter - match node's member_id tag to selected member_id + if (memberFilter) { + if (node.member_id !== memberFilter) return false; } return true; @@ -249,45 +249,36 @@ applyFiltersCore(); } - // Populate owner filter dropdown - function populateOwnerFilter() { - const select = document.getElementById('filter-owner'); + // Populate member filter dropdown + function populateMemberFilter() { + const select = document.getElementById('filter-member'); - // Get unique owners from nodes that have locations - const ownersWithNodes = new Set(); - allNodes.forEach(node => { - if (node.owner) { - ownersWithNodes.add(node.owner.public_key); + // Sort members by name + const sortedMembers = [...allMembers].sort((a, b) => a.name.localeCompare(b.name)); + + // Add options for all members + sortedMembers.forEach(member => { + if (member.member_id) { + const option = document.createElement('option'); + option.value = member.member_id; + option.textContent = member.callsign + ? `${member.name} (${member.callsign})` + : member.name; + select.appendChild(option); } }); - - // Filter members to only those who own nodes on the map - const relevantMembers = allMembers.filter(m => ownersWithNodes.has(m.public_key)); - - // Sort by name - relevantMembers.sort((a, b) => a.name.localeCompare(b.name)); - - // Add options - relevantMembers.forEach(member => { - const option = document.createElement('option'); - option.value = member.public_key; - option.textContent = member.callsign - ? `${member.name} (${member.callsign})` - : member.name; - select.appendChild(option); - }); } // Clear all filters function clearFilters() { document.getElementById('filter-type').value = ''; - document.getElementById('filter-owner').value = ''; + document.getElementById('filter-member').value = ''; applyFilters(); } // Event listeners for filters document.getElementById('filter-type').addEventListener('change', applyFilters); - document.getElementById('filter-owner').addEventListener('change', applyFilters); + document.getElementById('filter-member').addEventListener('change', applyFilters); document.getElementById('clear-filters').addEventListener('click', clearFilters); // Fetch and display nodes @@ -318,8 +309,8 @@ return; } - // Populate owner filter - populateOwnerFilter(); + // Populate member filter + populateMemberFilter(); // Initial display - center map on nodes if available if (allNodes.length > 0) {