+ {channels
+ .sort((a, b) =>
+ a.localBalance + a.remoteBalance > b.localBalance + b.remoteBalance
+ ? -1
+ : 1
+ )
+ .map((channel) => {
+ const node = nodes?.find(
+ (n) => n.public_key === channel.remotePubkey
+ );
+ const alias = node?.alias || "Unknown";
+ const capacity = channel.localBalance + channel.remoteBalance;
+
+ return (
+
+
+
+
+
+
+ {alias}
+
+
+
+
+
+
+
+
+
+ View Funding Transaction
+
+
+
+
+
+ View Node on amboss.space
+
+
+ {channel.public && (
+ editChannel(channel)}
+ >
+
+ Set Routing Fee
+
+ )}
+
+ closeChannel(
+ channel.id,
+ channel.remotePubkey,
+ channel.active
+ )
+ }
+ >
+
+ Close Channel
+
+
+
+
+
+
+
+
+
+ Status
+
+ {channel.status == "online" ? (
+
Online
+ ) : channel.status == "opening" ? (
+
Opening
+ ) : (
+
Offline
+ )}
+
+
+
+ Type
+
+
+ {channel.public ? "Public" : "Private"}
+
+
+
+
+ Capacity
+
+
+ {formatAmount(capacity)} sats
+
+
+
+
+
+
+
+
+
+ Funds each participant sets aside to discourage
+ cheating by ensuring each party has something at
+ stake. This reserve cannot be spent during the
+ channel's lifetime and typically amounts to 1% of
+ the channel capacity.
+
+
+
+
+ {channel.localBalance <
+ channel.unspendablePunishmentReserve * 1000 && (
+ <>
+ {formatAmount(
+ Math.min(
+ channel.localBalance,
+ channel.unspendablePunishmentReserve * 1000
+ )
+ )}{" "}
+ /{" "}
+ >
+ )}
+ {formatAmount(
+ channel.unspendablePunishmentReserve * 1000
+ )}{" "}
+ sats
+
+
+
+
+
+
+
+
+
+ Spending
+
+
+ Receiving
+
+
+
+
+
+
+
+ {formatAmount(channel.localSpendableBalance)} sats
+
+
+ {formatAmount(channel.remoteBalance)} sats
+
+
+
+
+
+
+
+ );
+ })}
+
+ >
+ );
+}
diff --git a/frontend/src/components/channels/ChannelsTable.tsx b/frontend/src/components/channels/ChannelsTable.tsx
new file mode 100644
index 00000000..aaf9cec2
--- /dev/null
+++ b/frontend/src/components/channels/ChannelsTable.tsx
@@ -0,0 +1,245 @@
+import {
+ ExternalLinkIcon,
+ HandCoins,
+ InfoIcon,
+ MoreHorizontal,
+ Trash2,
+} from "lucide-react";
+import { ChannelWarning } from "src/components/channels/ChannelWarning";
+import ExternalLink from "src/components/ExternalLink";
+import Loading from "src/components/Loading.tsx";
+import { Badge } from "src/components/ui/badge.tsx";
+import { Button } from "src/components/ui/button.tsx";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "src/components/ui/dropdown-menu.tsx";
+import { Progress } from "src/components/ui/progress.tsx";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "src/components/ui/table.tsx";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "src/components/ui/tooltip.tsx";
+import { formatAmount } from "src/lib/utils.ts";
+import { Channel, Node } from "src/types";
+
+type ChannelsTableProps = {
+ channels?: Channel[];
+ nodes?: Node[];
+ closeChannel(
+ channelId: string,
+ counterpartyNodeId: string,
+ isActive: boolean
+ ): void;
+ editChannel(channel: Channel): void;
+};
+
+export function ChannelsTable({
+ channels,
+ nodes,
+ closeChannel,
+ editChannel,
+}: ChannelsTableProps) {
+ if (channels && !channels.length) {
+ return null;
+ }
+
+ return (
+