import { LocationResponseDialog } from "@app/components/Dialog/LocationResponseDialog.tsx"; import { NodeOptionsDialog } from "@app/components/Dialog/NodeOptionsDialog.tsx"; import { TracerouteResponseDialog } from "@app/components/Dialog/TracerouteResponseDialog.tsx"; import Footer from "@app/components/UI/Footer.tsx"; import { Sidebar } from "@components/Sidebar.tsx"; import { Avatar } from "@components/UI/Avatar.tsx"; import { Mono } from "@components/generic/Mono.tsx"; import { Table } from "@components/generic/Table/index.tsx"; import { TimeAgo } from "@components/generic/TimeAgo.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf, type Types } from "@meshtastic/core"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { LockIcon, LockOpenIcon } from "lucide-react"; import { type JSX, useCallback, useEffect, useState } from "react"; import { base16 } from "rfc4648"; export interface DeleteNoteDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } function shortNameFromNode( node: ReturnType["nodes"][number], ): string { const shortNameOfNode = node.user?.shortName ?? (node.user?.macaddr ? `${base16 .stringify(node.user?.macaddr.subarray(4, 6) ?? []) .toLowerCase() }` : `${numberToHexUnpadded(node.num).slice(-4)}`); return String(shortNameOfNode); } const NodesPage = (): JSX.Element => { const { nodes, hardware, connection } = useDevice(); const [selectedNode, setSelectedNode] = useState< Protobuf.Mesh.NodeInfo | undefined >(undefined); const [selectedTraceroute, setSelectedTraceroute] = useState< Types.PacketMetadata | undefined >(); const [selectedLocation, setSelectedLocation] = useState< Types.PacketMetadata | undefined >(); const [searchTerm, setSearchTerm] = useState(""); const filteredNodes = Array.from(nodes.values()).filter((node) => { if (node.num === hardware.myNodeNum) return false; const nodeName = node.user?.longName ?? `!${numberToHexUnpadded(node.num)}`; return nodeName.toLowerCase().includes(searchTerm.toLowerCase()); }); useEffect(() => { if (!connection) return; connection.events.onTraceRoutePacket.subscribe(handleTraceroute); return () => { connection.events.onTraceRoutePacket.unsubscribe(handleTraceroute); }; }, [connection]); const handleTraceroute = useCallback( (traceroute: Types.PacketMetadata) => { setSelectedTraceroute(traceroute); }, [], ); useEffect(() => { if (!connection) return; connection.events.onPositionPacket.subscribe(handleLocation); return () => { connection.events.onPositionPacket.subscribe(handleLocation); }; }, [connection]); const handleLocation = useCallback( (location: Types.PacketMetadata) => { if (location.to.valueOf() !== hardware.myNodeNum) return; setSelectedLocation(location); }, [hardware.myNodeNum], ); return ( <>
setSearchTerm(e.target.value)} className="w-full p-2 border border-slate-300 rounded-sm bg-white text-slate-900 dark:bg-slate-700 dark:border-slate-600 dark:text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500" />
[
,

setSelectedNode(node)} onKeyUp={(evt) => { evt.key === "Enter" && setSelectedNode(node); }} className="cursor-pointer underline" tabIndex={0} role="button" > {node.user?.longName ?? (node.user?.macaddr ? `Meshtastic ${base16 .stringify(node.user?.macaddr.subarray(4, 6) ?? []) .toLowerCase() }` : `!${numberToHexUnpadded(node.num)}`)}

, {node.lastHeard !== 0 ? node.viaMqtt === false && node.hopsAway === 0 ? "Direct" : `${node.hopsAway?.toString()} ${node.hopsAway > 1 ? "hops" : "hop" } away` : "-"} {node.viaMqtt === true ? ", via MQTT" : ""} , {node.lastHeard === 0 ?

Never

: }
, {node.user?.publicKey && node.user?.publicKey.length > 0 ? : } , {node.snr}db/ {Math.min(Math.max((node.snr + 10) * 5, 0), 100)}%/ {(node.snr + 10) * 5}raw , {Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0]} , {base16 .stringify(node.user?.macaddr ?? []) .match(/.{1,2}/g) ?.join(":") ?? "UNK"} , ])} /> setSelectedNode(undefined)} /> setSelectedTraceroute(undefined)} /> setSelectedLocation(undefined)} />
); }; export default NodesPage;