Skip to content

Commit

Permalink
chore: add disconnect peer dialog (#434)
Browse files Browse the repository at this point in the history
* feat(lnd): add disconnect peer

* chore: add disconnect peer dialog

* fix: add break-all to toast text

* chore: render dialog component outside table

* chore: remove check for node id

* chore: minor changes

* fix: usage of break all

* chore: rename

* chore: undo break-all change on the toast component

* chore: fix naming

* fix: word wrap on toast content

* fix: disconnect peer toasts

* chore: rename dialog content components

* chore: minor copy improvements

---------

Co-authored-by: Roland Bewick <[email protected]>
  • Loading branch information
im-adithya and rolznz authored Aug 12, 2024
1 parent d9a01d9 commit acd95ff
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Props = {
channel: Channel;
};

export function CloseChannelDialog({ alias, channel }: Props) {
export function CloseChannelDialogContent({ alias, channel }: Props) {
const [closeType, setCloseType] = React.useState("normal");
const [step, setStep] = React.useState(channel.active ? 2 : 1);
const [fundingTxId, setFundingTxId] = React.useState("");
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/components/DisconnectPeerDialogContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { toast } from "src/components/ui/use-toast";
import { usePeers } from "src/hooks/usePeers";
import { Peer } from "src/types";
import { request } from "src/utils/request";
import {
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "./ui/alert-dialog";

type Props = {
peer: Peer;
name: string | undefined;
};

export function DisconnectPeerDialogContent({ peer, name }: Props) {
const { mutate: reloadPeers } = usePeers();

async function disconnectPeer() {
try {
console.info(`Disconnecting from ${peer.nodeId}`);

await request(`/api/peers/${peer.nodeId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
toast({
title: "Successfully disconnected from peer",
description: peer.nodeId,
});
await reloadPeers();
} catch (e) {
toast({
variant: "destructive",
title: "Failed to disconnect peer",
description: "" + e,
});
console.error(e);
}
}

return (
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Disconnect Peer</AlertDialogTitle>
<AlertDialogDescription>
<div>
<p>
Are you sure you wish to disconnect from {name || "this peer"}?
</p>
<p className="text-primary font-medium mt-4">Peer Pubkey</p>
<p className="break-all">{peer.nodeId}</p>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={disconnectPeer}>Confirm</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
);
}
4 changes: 2 additions & 2 deletions frontend/src/components/channels/ChannelDropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
MoreHorizontal,
Trash2,
} from "lucide-react";
import { CloseChannelDialog } from "src/components/CloseChannelDialog";
import { CloseChannelDialogContent } from "src/components/CloseChannelDialogContent";
import ExternalLink from "src/components/ExternalLink";
import {
AlertDialog,
Expand Down Expand Up @@ -74,7 +74,7 @@ export function ChannelDropdownMenu({
</AlertDialogTrigger>
</DropdownMenuContent>
</DropdownMenu>
<CloseChannelDialog alias={alias} channel={channel} />
<CloseChannelDialogContent alias={alias} channel={channel} />
</AlertDialog>
);
}
23 changes: 13 additions & 10 deletions frontend/src/components/ui/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";
import { Cross2Icon } from "@radix-ui/react-icons";
import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";

import { cn } from "src/lib/utils";

Expand Down Expand Up @@ -92,7 +92,10 @@ const ToastTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
className={cn(
"break-words max-w-xs text-sm font-semibold [&+div]:text-xs",
className
)}
{...props}
/>
));
Expand All @@ -104,7 +107,7 @@ const ToastDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
className={cn("break-words max-w-xs text-sm opacity-90", className)}
{...props}
/>
));
Expand All @@ -115,13 +118,13 @@ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>;

export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
type ToastActionElement,
type ToastProps,
};
60 changes: 34 additions & 26 deletions frontend/src/screens/peers/Peers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { MoreHorizontal, Trash2 } from "lucide-react";
import React from "react";
import { Link } from "react-router-dom";
import AppHeader from "src/components/AppHeader.tsx";
import { DisconnectPeerDialogContent } from "src/components/DisconnectPeerDialogContent";
import { AlertDialog } from "src/components/ui/alert-dialog.tsx";
import { Badge } from "src/components/ui/badge.tsx";
import { Button } from "src/components/ui/button.tsx";
import {
Expand All @@ -18,18 +20,19 @@ import {
TableHeader,
TableRow,
} from "src/components/ui/table.tsx";
import { toast } from "src/components/ui/use-toast.ts";
import { toast } from "src/components/ui/use-toast";
import { useChannels } from "src/hooks/useChannels";
import { usePeers } from "src/hooks/usePeers.ts";
import { useSyncWallet } from "src/hooks/useSyncWallet.ts";
import { Node } from "src/types";
import { Node, Peer } from "src/types";
import { request } from "src/utils/request";

export default function Peers() {
useSyncWallet();
const { data: peers, mutate: reloadPeers } = usePeers();
const { data: peers } = usePeers();
const { data: channels } = useChannels();
const [nodes, setNodes] = React.useState<Node[]>([]);
const [peerToDisconnect, setPeerToDisconnect] = React.useState<Peer>();

// TODO: move to NWC backend
const loadNodeStats = React.useCallback(async () => {
Expand All @@ -56,35 +59,22 @@ export default function Peers() {
loadNodeStats();
}, [loadNodeStats]);

async function disconnectPeer(peerId: string) {
async function checkDisconnectPeer(peer: Peer) {
try {
if (!peerId) {
throw new Error("peer missing");
}
if (!channels) {
throw new Error("channels not loaded");
}
if (channels.some((channel) => channel.remotePubkey === peerId)) {
throw new Error("you have one or more open channels with " + peerId);
}
if (
!confirm(
"Are you sure you wish to disconnect with peer " + peerId + "?"
)
) {
return;
if (channels.some((channel) => channel.remotePubkey === peer.nodeId)) {
throw new Error(
"you have one or more open channels with " + peer.nodeId
);
}
console.info(`Disconnecting from ${peerId}`);

await request(`/api/peers/${peerId}`, {
method: "DELETE",
});
toast({ title: "Successfully disconnected from peer " + peerId });
await reloadPeers();
setPeerToDisconnect(peer);
} catch (e) {
toast({
variant: "destructive",
title: "Failed to disconnect peer: " + e,
title: "Cannot disconnect peer",
description: "" + e,
});
console.error(e);
}
Expand Down Expand Up @@ -152,10 +142,10 @@ export default function Peers() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => checkDisconnectPeer(peer)}
className="flex flex-row items-center gap-2"
onClick={() => disconnectPeer(peer.nodeId)}
>
<Trash2 className="text-destructive" />
<Trash2 className="h-4 w-4 text-destructive" />
Disconnect Peer
</DropdownMenuItem>
</DropdownMenuContent>
Expand All @@ -167,6 +157,24 @@ export default function Peers() {
</>
</TableBody>
</Table>

<AlertDialog
open={!!peerToDisconnect}
onOpenChange={(open) => {
if (!open) {
setPeerToDisconnect(undefined);
}
}}
>
{peerToDisconnect && (
<DisconnectPeerDialogContent
peer={peerToDisconnect}
name={
nodes.find((n) => n.public_key === peerToDisconnect.nodeId)?.alias
}
/>
)}
</AlertDialog>
</>
);
}
5 changes: 5 additions & 0 deletions lnclient/lnd/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,11 @@ func (svc *LNDService) UpdateChannel(ctx context.Context, updateChannelRequest *
}

func (svc *LNDService) DisconnectPeer(ctx context.Context, peerId string) error {
_, err := svc.client.DisconnectPeer(ctx, &lnrpc.DisconnectPeerRequest{PubKey: peerId})
if err != nil {
return err
}

return nil
}

Expand Down
4 changes: 4 additions & 0 deletions lnclient/lnd/wrapper/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,7 @@ func (wrapper *LNDWrapper) GetChanInfo(ctx context.Context, req *lnrpc.ChanInfoR
func (wrapper *LNDWrapper) UpdateChannel(ctx context.Context, req *lnrpc.PolicyUpdateRequest, options ...grpc.CallOption) (*lnrpc.PolicyUpdateResponse, error) {
return wrapper.client.UpdateChannelPolicy(ctx, req, options...)
}

func (wrapper *LNDWrapper) DisconnectPeer(ctx context.Context, req *lnrpc.DisconnectPeerRequest, options ...grpc.CallOption) (*lnrpc.DisconnectPeerResponse, error) {
return wrapper.client.DisconnectPeer(ctx, req, options...)
}

0 comments on commit acd95ff

Please sign in to comment.