diff --git a/src/components/AdvertDetails.tsx b/src/components/AdvertDetails.tsx index 8e9705e..03ad0a4 100644 --- a/src/components/AdvertDetails.tsx +++ b/src/components/AdvertDetails.tsx @@ -17,6 +17,7 @@ interface AdvertDetailsProps { is_chat_node: number; is_room_server: number; has_location: number; + packet_hash: string; }; initiatingNodeKey?: string; } @@ -97,6 +98,7 @@ export default function AdvertDetails({ advert, initiatingNodeKey }: AdvertDetai }))} className="text-sm" initiatingNodeKey={initiatingNodeKey} + packetHash={advert.packet_hash} /> diff --git a/src/components/ChatMessageItem.tsx b/src/components/ChatMessageItem.tsx index 22cfee8..146f4a2 100644 --- a/src/components/ChatMessageItem.tsx +++ b/src/components/ChatMessageItem.tsx @@ -160,6 +160,7 @@ function ChatMessageItem({ msg, showErrorRow }: { msg: ChatMessage, showErrorRow paths={pathData} title={`Heard ${pathData.length} repeat${pathData.length !== 1 ? 's' : ''}`} className="text-xs" + packetHash={msg.message_id} /> ); @@ -180,6 +181,7 @@ function ChatMessageItem({ msg, showErrorRow }: { msg: ChatMessage, showErrorRow paths={pathData} title={`Heard ${pathData.length} repeat${pathData.length !== 1 ? 's' : ''}`} className="text-xs" + packetHash={msg.message_id} /> ); @@ -200,6 +202,7 @@ function ChatMessageItem({ msg, showErrorRow }: { msg: ChatMessage, showErrorRow paths={pathData} title={`Heard ${pathData.length} repeat${pathData.length !== 1 ? 's' : ''}`} className="text-xs" + packetHash={msg.message_id} /> ); diff --git a/src/components/PathVisualization.tsx b/src/components/PathVisualization.tsx index 1609153..85f1774 100644 --- a/src/components/PathVisualization.tsx +++ b/src/components/PathVisualization.tsx @@ -5,6 +5,7 @@ import { createPortal } from "react-dom"; import Link from "next/link"; import Tree from 'react-d3-tree'; import { ArrowsPointingOutIcon, ArrowsPointingInIcon } from "@heroicons/react/24/outline"; +import { ExternalLink } from "lucide-react"; import PathDisplay from "./PathDisplay"; import { useMeshcoreSearches } from "@/hooks/useMeshcoreSearch"; import type { MeshcoreSearchResult } from "@/hooks/useMeshcoreSearch"; @@ -33,6 +34,7 @@ interface PathVisualizationProps { className?: string; showDropdown?: boolean; initiatingNodeKey?: string; + packetHash?: string; } @@ -41,7 +43,8 @@ export default function PathVisualization({ title = "Paths", className = "", showDropdown = true, - initiatingNodeKey + initiatingNodeKey, + packetHash }: PathVisualizationProps) { const [expanded, setExpanded] = useState(false); const [showGraph, setShowGraph] = useState(false); @@ -340,6 +343,17 @@ export default function PathVisualization({ {showGraph ? 'Hide Graph' : 'Show Graph'} )} + {packetHash && ( + + Analyze + + + )} {showGraph && } @@ -373,6 +387,18 @@ export default function PathVisualization({ {showGraph ? 'Hide Graph' : 'Show Graph'} )} + + {packetHash && ( + + Analyze + + + )} {expanded && pathsCount > 0 && ( diff --git a/src/hooks/useNodeData.ts b/src/hooks/useNodeData.ts index 226e8ff..a5cfd49 100644 --- a/src/hooks/useNodeData.ts +++ b/src/hooks/useNodeData.ts @@ -29,6 +29,7 @@ export interface Advert { is_chat_node: number; is_room_server: number; has_location: number; + packet_hash: string; } export interface LocationHistory { diff --git a/src/lib/clickhouse/actions.ts b/src/lib/clickhouse/actions.ts index 2aab405..581fb8c 100644 --- a/src/lib/clickhouse/actions.ts +++ b/src/lib/clickhouse/actions.ts @@ -157,7 +157,7 @@ export async function getMeshcoreNodeInfo(publicKey: string, limit: number = 50) // Get recent adverts grouped by adv_timestamp with origin_path_pubkey tuples const advertsQuery = ` SELECT - adv_timestamp, + argMax(adv_timestamp, ingest_timestamp) as adv_timestamp, groupArray((origin, path, origin_pubkey)) as origin_path_pubkey_tuples, count() as advert_count, min(ingest_timestamp) as earliest_timestamp, @@ -167,7 +167,8 @@ export async function getMeshcoreNodeInfo(publicKey: string, limit: number = 50) argMax(is_repeater, ingest_timestamp) as is_repeater, argMax(is_chat_node, ingest_timestamp) as is_chat_node, argMax(is_room_server, ingest_timestamp) as is_room_server, - argMax(has_location, ingest_timestamp) as has_location + argMax(has_location, ingest_timestamp) as has_location, + packet_hash FROM ( SELECT ingest_timestamp, @@ -182,12 +183,13 @@ export async function getMeshcoreNodeInfo(publicKey: string, limit: number = 50) is_room_server, has_location, hex(origin_pubkey) as origin_pubkey, - origin + origin, + packet_hash FROM meshcore_adverts WHERE public_key = {publicKey:String} ORDER BY ingest_timestamp DESC ) - GROUP BY adv_timestamp + GROUP BY packet_hash ORDER BY max(ingest_timestamp) DESC LIMIT {limit:UInt32} `;