Merge pull request #211 from fifieldt/traceroute
Add Traceroute Feature
This commit is contained in:
@@ -5,42 +5,68 @@ import {
|
|||||||
} from "@app/core/stores/deviceStore.js";
|
} from "@app/core/stores/deviceStore.js";
|
||||||
import { Message } from "@components/PageComponents/Messages/Message.js";
|
import { Message } from "@components/PageComponents/Messages/Message.js";
|
||||||
import { MessageInput } from "@components/PageComponents/Messages/MessageInput.js";
|
import { MessageInput } from "@components/PageComponents/Messages/MessageInput.js";
|
||||||
import type { Types } from "@meshtastic/js";
|
import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.js";
|
||||||
|
import type { Protobuf, Types } from "@meshtastic/js";
|
||||||
import { InboxIcon } from "lucide-react";
|
import { InboxIcon } from "lucide-react";
|
||||||
|
|
||||||
export interface ChannelChatProps {
|
export interface ChannelChatProps {
|
||||||
messages?: MessageWithState[];
|
messages?: MessageWithState[];
|
||||||
channel: Types.ChannelNumber;
|
channel: Types.ChannelNumber;
|
||||||
to: Types.Destination;
|
to: Types.Destination;
|
||||||
|
traceroutes?: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChannelChat = ({
|
export const ChannelChat = ({
|
||||||
messages,
|
messages,
|
||||||
channel,
|
channel,
|
||||||
to,
|
to,
|
||||||
|
traceroutes,
|
||||||
}: ChannelChatProps): JSX.Element => {
|
}: ChannelChatProps): JSX.Element => {
|
||||||
const { nodes } = useDevice();
|
const { nodes } = useDevice();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-grow flex-col">
|
<div className="flex flex-grow flex-col">
|
||||||
<div className="flex flex-grow flex-col">
|
<div className="flex flex-grow">
|
||||||
{messages ? (
|
<div className="flex flex-grow flex-col">
|
||||||
messages.map((message, index) => (
|
{messages ? (
|
||||||
<Message
|
messages.map((message, index) => (
|
||||||
key={message.id}
|
<Message
|
||||||
message={message}
|
key={message.id}
|
||||||
lastMsgSameUser={
|
message={message}
|
||||||
index === 0 ? false : messages[index - 1].from === message.from
|
lastMsgSameUser={
|
||||||
}
|
index === 0
|
||||||
sender={nodes.get(message.from)}
|
? false
|
||||||
/>
|
: messages[index - 1].from === message.from
|
||||||
))
|
}
|
||||||
) : (
|
sender={nodes.get(message.from)}
|
||||||
<div className="m-auto">
|
/>
|
||||||
<InboxIcon className="m-auto" />
|
))
|
||||||
<Subtle>No Messages</Subtle>
|
) : (
|
||||||
</div>
|
<div className="m-auto">
|
||||||
)}
|
<InboxIcon className="m-auto" />
|
||||||
|
<Subtle>No Messages</Subtle>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`flex flex-grow flex-col border-slate-400 border-l ${traceroutes === undefined ? "hidden" : ""}`}
|
||||||
|
>
|
||||||
|
{to === "broadcast" ? null : traceroutes ? (
|
||||||
|
traceroutes.map((traceroute, index) => (
|
||||||
|
<TraceRoute
|
||||||
|
key={traceroute.id}
|
||||||
|
from={nodes.get(traceroute.from)}
|
||||||
|
to={nodes.get(traceroute.to)}
|
||||||
|
route={traceroute.data.route}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="m-auto">
|
||||||
|
<InboxIcon className="m-auto" />
|
||||||
|
<Subtle>No Traceroutes</Subtle>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3">
|
<div className="p-3">
|
||||||
<MessageInput to={to} channel={channel} />
|
<MessageInput to={to} channel={channel} />
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||||
|
import type { Protobuf } from "@meshtastic/js";
|
||||||
|
|
||||||
|
export interface TraceRouteProps {
|
||||||
|
from?: Protobuf.Mesh.NodeInfo;
|
||||||
|
to?: Protobuf.Mesh.NodeInfo;
|
||||||
|
route: Array<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TraceRoute = ({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
route,
|
||||||
|
}: TraceRouteProps): JSX.Element => {
|
||||||
|
const { nodes } = useDevice();
|
||||||
|
|
||||||
|
return route.length === 0 ? (
|
||||||
|
<div className="ml-5 flex">
|
||||||
|
<span className="ml-4 border-l-2 border-l-backgroundPrimary pl-2 text-textPrimary">
|
||||||
|
{to?.user?.longName}↔{from?.user?.longName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="ml-5 flex">
|
||||||
|
<span className="ml-4 border-l-2 border-l-backgroundPrimary pl-2 text-textPrimary">
|
||||||
|
{to?.user?.longName}↔
|
||||||
|
{route.map((hop) => `${nodes.get(hop)?.user?.longName ?? "Unknown"}↔`)}
|
||||||
|
{from?.user?.longName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -42,6 +42,10 @@ export interface Device {
|
|||||||
direct: Map<number, MessageWithState[]>;
|
direct: Map<number, MessageWithState[]>;
|
||||||
broadcast: Map<Types.ChannelNumber, MessageWithState[]>;
|
broadcast: Map<Types.ChannelNumber, MessageWithState[]>;
|
||||||
};
|
};
|
||||||
|
traceroutes: Map<
|
||||||
|
number,
|
||||||
|
Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>[]
|
||||||
|
>;
|
||||||
connection?: Types.ConnectionType;
|
connection?: Types.ConnectionType;
|
||||||
activePage: Page;
|
activePage: Page;
|
||||||
activeNode: number;
|
activeNode: number;
|
||||||
@@ -75,6 +79,9 @@ export interface Device {
|
|||||||
addPosition: (position: Types.PacketMetadata<Protobuf.Mesh.Position>) => void;
|
addPosition: (position: Types.PacketMetadata<Protobuf.Mesh.Position>) => void;
|
||||||
addConnection: (connection: Types.ConnectionType) => void;
|
addConnection: (connection: Types.ConnectionType) => void;
|
||||||
addMessage: (message: MessageWithState) => void;
|
addMessage: (message: MessageWithState) => void;
|
||||||
|
addTraceRoute: (
|
||||||
|
traceroute: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>,
|
||||||
|
) => void;
|
||||||
addMetadata: (from: number, metadata: Protobuf.Mesh.DeviceMetadata) => void;
|
addMetadata: (from: number, metadata: Protobuf.Mesh.DeviceMetadata) => void;
|
||||||
removeNode: (nodeNum: number) => void;
|
removeNode: (nodeNum: number) => void;
|
||||||
setMessageState: (
|
setMessageState: (
|
||||||
@@ -122,6 +129,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
|
|||||||
direct: new Map(),
|
direct: new Map(),
|
||||||
broadcast: new Map(),
|
broadcast: new Map(),
|
||||||
},
|
},
|
||||||
|
traceroutes: new Map(),
|
||||||
connection: undefined,
|
connection: undefined,
|
||||||
activePage: "messages",
|
activePage: "messages",
|
||||||
activeNode: 0,
|
activeNode: 0,
|
||||||
@@ -487,6 +495,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
addMetadata: (from, metadata) => {
|
addMetadata: (from, metadata) => {
|
||||||
set(
|
set(
|
||||||
produce<DeviceState>((draft) => {
|
produce<DeviceState>((draft) => {
|
||||||
@@ -498,6 +507,26 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
addTraceRoute: (traceroute) => {
|
||||||
|
set(
|
||||||
|
produce<DeviceState>((draft) => {
|
||||||
|
console.log("addTraceRoute called");
|
||||||
|
console.log(traceroute);
|
||||||
|
const device = draft.devices.get(id);
|
||||||
|
if (!device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodetraceroutes = device.traceroutes.get(traceroute.from);
|
||||||
|
if (nodetraceroutes) {
|
||||||
|
nodetraceroutes.push(traceroute);
|
||||||
|
device.traceroutes.set(traceroute.from, nodetraceroutes);
|
||||||
|
} else {
|
||||||
|
device.traceroutes.set(traceroute.from, [traceroute]);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
removeNode: (nodeNum) => {
|
removeNode: (nodeNum) => {
|
||||||
set(
|
set(
|
||||||
produce<DeviceState>((draft) => {
|
produce<DeviceState>((draft) => {
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ export const subscribeAll = (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connection.events.onTraceRoutePacket.subscribe((traceRoutePacket) => {
|
||||||
|
device.addTraceRoute({
|
||||||
|
...traceRoutePacket,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
connection.events.onPendingSettingsChange.subscribe((state) => {
|
connection.events.onPendingSettingsChange.subscribe((state) => {
|
||||||
device.setPendingSettingsChanges(state);
|
device.setPendingSettingsChanges(state);
|
||||||
});
|
});
|
||||||
|
|||||||
+27
-2
@@ -3,15 +3,17 @@ import { PageLayout } from "@components/PageLayout.js";
|
|||||||
import { Sidebar } from "@components/Sidebar.js";
|
import { Sidebar } from "@components/Sidebar.js";
|
||||||
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js";
|
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js";
|
||||||
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
|
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
|
||||||
|
import { useToast } from "@core/hooks/useToast.js";
|
||||||
import { useDevice } from "@core/stores/deviceStore.js";
|
import { useDevice } from "@core/stores/deviceStore.js";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||||
import { Protobuf, Types } from "@meshtastic/js";
|
import { Protobuf, Types } from "@meshtastic/js";
|
||||||
import { getChannelName } from "@pages/Channels.js";
|
import { getChannelName } from "@pages/Channels.js";
|
||||||
import { HashIcon } from "lucide-react";
|
import { HashIcon, WaypointsIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const MessagesPage = (): JSX.Element => {
|
export const MessagesPage = (): JSX.Element => {
|
||||||
const { channels, nodes, hardware, messages } = useDevice();
|
const { channels, nodes, hardware, messages, traceroutes, connection } =
|
||||||
|
useDevice();
|
||||||
const [chatType, setChatType] =
|
const [chatType, setChatType] =
|
||||||
useState<Types.PacketDestination>("broadcast");
|
useState<Types.PacketDestination>("broadcast");
|
||||||
const [activeChat, setActiveChat] = useState<number>(
|
const [activeChat, setActiveChat] = useState<number>(
|
||||||
@@ -25,6 +27,7 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
(ch) => ch.role !== Protobuf.Channel.Channel_Role.DISABLED,
|
(ch) => ch.role !== Protobuf.Channel.Channel_Role.DISABLED,
|
||||||
);
|
);
|
||||||
const currentChannel = channels.get(activeChat);
|
const currentChannel = channels.get(activeChat);
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -72,6 +75,27 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
? nodes.get(activeChat)?.user?.longName ?? "Unknown"
|
? nodes.get(activeChat)?.user?.longName ?? "Unknown"
|
||||||
: "Loading..."
|
: "Loading..."
|
||||||
}`}
|
}`}
|
||||||
|
actions={
|
||||||
|
chatType === "direct"
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
icon: WaypointsIcon,
|
||||||
|
async onClick() {
|
||||||
|
const targetNode = nodes.get(activeChat)?.num;
|
||||||
|
if (targetNode === undefined) return;
|
||||||
|
toast({
|
||||||
|
title: "Sending Traceroute, please wait...",
|
||||||
|
});
|
||||||
|
await connection?.traceRoute(targetNode).then(() =>
|
||||||
|
toast({
|
||||||
|
title: "Traceroute sent.",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{allChannels.map(
|
{allChannels.map(
|
||||||
(channel) =>
|
(channel) =>
|
||||||
@@ -92,6 +116,7 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
to={activeChat}
|
to={activeChat}
|
||||||
messages={messages.direct.get(node.num)}
|
messages={messages.direct.get(node.num)}
|
||||||
channel={Types.ChannelNumber.Primary}
|
channel={Types.ChannelNumber.Primary}
|
||||||
|
traceroutes={traceroutes.get(node.num)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user