Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request]: Nodes quick options #307

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions src/components/Dialog/NodeOptionsDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { toast } from "@app/core/hooks/useToast";
import { useDevice } from "@app/core/stores/deviceStore";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.js";
import type { Protobuf } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { Button } from "../UI/Button";

export interface NodeOptionsDialogProps {
node: Protobuf.Mesh.NodeInfo | undefined;
open: boolean;
onOpenChange: () => void;
}

export const NodeOptionsDialog = ({
node,
open,
onOpenChange,
}: NodeOptionsDialogProps): JSX.Element => {
const { connection } = useDevice();
const longName =
node?.user?.longName ??
(node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown");
const shortName =
node?.user?.shortName ??
(node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK");

function handleTraceroute() {
if (!node) return;
toast({
title: "Sending Traceroute, please wait...",
});
connection?.traceRoute(node.num).then(() =>
toast({
title: "Traceroute sent.",
}),
);
onOpenChange();
}

function handleRequestPosition() {
if (!node) return;
toast({
title: "Requesting position, please wait...",
});
connection?.requestPosition(node.num).then(() =>
toast({
title: "Position request sent.",
}),
);
onOpenChange();
}

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{`${longName} (${shortName})`}</DialogTitle>
</DialogHeader>
<div className="flex flex-col space-y-1">
<div>
<Button onClick={handleTraceroute}>Trace Route</Button>
</div>
<div>
<Button onClick={handleRequestPosition}>Request Position</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
};
46 changes: 46 additions & 0 deletions src/components/Dialog/TracerouteResponseDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useDevice } from "@app/core/stores/deviceStore";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.js";
import type { Protobuf, Types } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { TraceRoute } from "../PageComponents/Messages/TraceRoute";

export interface TracerouteResponseDialogProps {
traceroute: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined;
open: boolean;
onOpenChange: () => void;
}

export const TracerouteResponseDialog = ({
traceroute,
open,
onOpenChange,
}: TracerouteResponseDialogProps): JSX.Element => {
const { nodes } = useDevice();
const route: number[] = traceroute?.data.route ?? [];
const from = nodes.get(traceroute?.from ?? 0);
const longName =
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName =
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
const to = nodes.get(traceroute?.to ?? 0);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{`Traceroute: ${longName} (${shortName})`}</DialogTitle>
</DialogHeader>
<DialogDescription>
<TraceRoute route={route} from={from} to={to} />
</DialogDescription>
</DialogContent>
</Dialog>
);
};
12 changes: 6 additions & 6 deletions src/components/PageComponents/Messages/TraceRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ export const TraceRoute = ({
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}
{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) => {
const node = nodes.get(hop);
return `${node?.user?.longName ?? (node?.num ? numberToHexUnpadded(node.num) : "Unknown")}↔`;
})}
{to?.user?.longName} ↔{" "}
{route.map(
(hop) =>
`${nodes.get(hop)?.user?.longName ?? `!${numberToHexUnpadded(hop)}`} ↔ `,
)}
{from?.user?.longName}
</span>
</div>
Expand Down
42 changes: 37 additions & 5 deletions src/pages/Nodes.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { NodeOptionsDialog } from "@app/components/Dialog/NodeOptionsDialog";
import { TracerouteResponseDialog } from "@app/components/Dialog/TracerouteResponseDialog";
import Footer from "@app/components/UI/Footer";
import { useAppStore } from "@app/core/stores/appStore";
import { Sidebar } from "@components/Sidebar.js";
Expand All @@ -7,10 +9,10 @@ import { Table } from "@components/generic/Table/index.js";
import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/js";
import { Protobuf, type Types } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { LockIcon, LockOpenIcon, TrashIcon } from "lucide-react";
import { Fragment } from "react";
import { Fragment, useCallback, useEffect, useState } from "react";
import { base16 } from "rfc4648";

export interface DeleteNoteDialogProps {
Expand All @@ -19,13 +21,33 @@ export interface DeleteNoteDialogProps {
}

export const NodesPage = (): JSX.Element => {
const { nodes, hardware, setDialogOpen } = useDevice();
const { nodes, hardware, setDialogOpen, connection } = useDevice();
const { setNodeNumToBeRemoved } = useAppStore();
const [selectedNode, setSelectedNode] = useState<
Protobuf.Mesh.NodeInfo | undefined
>(undefined);
const [selectedTraceroute, setSelectedTraceroute] = useState<
Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined
>();

const filteredNodes = Array.from(nodes.values()).filter(
(n) => n.num !== hardware.myNodeNum,
);

useEffect(() => {
connection?.events.onTraceRoutePacket.subscribe(handleTraceroute);
return () => {
connection?.events.onTraceRoutePacket.unsubscribe(handleTraceroute);
};
}, [connection]);

const handleTraceroute = useCallback(
(traceroute: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>) => {
setSelectedTraceroute(traceroute);
},
[],
);

return (
<>
<Sidebar />
Expand All @@ -45,7 +67,7 @@ export const NodesPage = (): JSX.Element => {
]}
rows={filteredNodes.map((node) => [
<Hashicon key="icon" size={24} value={node.num.toString()} />,
<h1 key="header">
<h1 className="cursor-pointer" key="header" onMouseDown={() => setSelectedNode(node)}>
{node.user?.longName ??
(node.user?.macaddr
? `Meshtastic ${base16
Expand Down Expand Up @@ -79,7 +101,7 @@ export const NodesPage = (): JSX.Element => {
{node.user?.publicKey && node.user?.publicKey.length > 0 ? (
<LockIcon className="text-green-600" />
) : (
<LockOpenIcon className="text-yellow-300" />
<LockOpenIcon className="text-yellow-300 mx-auto" />
)}
</Mono>,
<Mono key="hops">
Expand All @@ -105,6 +127,16 @@ export const NodesPage = (): JSX.Element => {
</Button>,
])}
/>
<NodeOptionsDialog
node={selectedNode}
open={!!selectedNode}
onOpenChange={() => setSelectedNode(undefined)}
/>
<TracerouteResponseDialog
traceroute={selectedTraceroute}
open={!!selectedTraceroute}
onOpenChange={() => setSelectedTraceroute(undefined)}
/>
</div>
<Footer />
</div>
Expand Down
Loading