From fd1a15c1b48768aec43389c4b92930339e46819c Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Mon, 8 Apr 2024 14:07:24 +0200 Subject: [PATCH] Change exit node button, add indicator to peers table --- src/app/(dashboard)/peer/page.tsx | 4 +- src/components/modal/ModalHeader.tsx | 14 ++- src/components/ui/TextWithTooltip.tsx | 2 +- src/modules/exit-node/AddExitNodeButton.tsx | 19 +++- .../exit-node/ExitNodePeerIndicator.tsx | 12 ++- src/modules/exit-node/useHasExitNodes.tsx | 10 +++ src/modules/peer/PeerRoutesTable.tsx | 11 +++ src/modules/routes/RouteModal.tsx | 88 +++++++++++++------ 8 files changed, 116 insertions(+), 44 deletions(-) create mode 100644 src/modules/exit-node/useHasExitNodes.tsx diff --git a/src/app/(dashboard)/peer/page.tsx b/src/app/(dashboard)/peer/page.tsx index d672f35f..bda0bac6 100644 --- a/src/app/(dashboard)/peer/page.tsx +++ b/src/app/(dashboard)/peer/page.tsx @@ -58,6 +58,7 @@ import { OperatingSystem } from "@/interfaces/OperatingSystem"; import type { Peer } from "@/interfaces/Peer"; import PageContainer from "@/layouts/PageContainer"; import { AddExitNodeButton } from "@/modules/exit-node/AddExitNodeButton"; +import { useHasExitNodes } from "@/modules/exit-node/useHasExitNodes"; import useGroupHelper from "@/modules/groups/useGroupHelper"; import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton"; import PeerRoutesTable from "@/modules/peer/PeerRoutesTable"; @@ -128,6 +129,7 @@ function PeerOverview() { }; const { isUser } = useLoggedInUser(); + const hasExitNodes = useHasExitNodes(peer); return ( @@ -344,7 +346,7 @@ function PeerOverview() {
- +
diff --git a/src/components/modal/ModalHeader.tsx b/src/components/modal/ModalHeader.tsx index 88aae795..251febc3 100644 --- a/src/components/modal/ModalHeader.tsx +++ b/src/components/modal/ModalHeader.tsx @@ -9,6 +9,7 @@ interface Props extends IconVariant { description: string | React.ReactNode; className?: string; margin?: string; + truncate?: boolean; } export default function ModalHeader({ icon, @@ -17,14 +18,19 @@ export default function ModalHeader({ color = "netbird", className = "pb-6 px-8", margin = "mt-0", + truncate = false, }: Props) { return ( -
-
+
+
{icon && } -
+

{title}

- {description} + + {description} +
diff --git a/src/components/ui/TextWithTooltip.tsx b/src/components/ui/TextWithTooltip.tsx index 33f7f6e4..b66c3605 100644 --- a/src/components/ui/TextWithTooltip.tsx +++ b/src/components/ui/TextWithTooltip.tsx @@ -34,7 +34,7 @@ export default function TextWithTooltip({
{text}
diff --git a/src/modules/exit-node/AddExitNodeButton.tsx b/src/modules/exit-node/AddExitNodeButton.tsx index af46c5a7..eac54959 100644 --- a/src/modules/exit-node/AddExitNodeButton.tsx +++ b/src/modules/exit-node/AddExitNodeButton.tsx @@ -1,6 +1,6 @@ import Button from "@components/Button"; import { Modal } from "@components/modal/Modal"; -import { IconCirclePlus } from "@tabler/icons-react"; +import { IconCirclePlus, IconDirectionSign } from "@tabler/icons-react"; import * as React from "react"; import { useState } from "react"; import { Peer } from "@/interfaces/Peer"; @@ -9,16 +9,26 @@ import { RouteModalContent } from "@/modules/routes/RouteModal"; type Props = { peer?: Peer; + firstTime?: boolean; }; -export const AddExitNodeButton = ({ peer }: Props) => { +export const AddExitNodeButton = ({ peer, firstTime = false }: Props) => { const [modal, setModal] = useState(false); return ( <> @@ -26,6 +36,7 @@ export const AddExitNodeButton = ({ peer }: Props) => { setModal(false)} peer={peer} + isFirstExitNode={firstTime} exitNode={true} /> )} diff --git a/src/modules/exit-node/ExitNodePeerIndicator.tsx b/src/modules/exit-node/ExitNodePeerIndicator.tsx index 1a6d1029..213c1210 100644 --- a/src/modules/exit-node/ExitNodePeerIndicator.tsx +++ b/src/modules/exit-node/ExitNodePeerIndicator.tsx @@ -1,23 +1,21 @@ import FullTooltip from "@components/FullTooltip"; import { IconDirectionSign } from "@tabler/icons-react"; -import useFetchApi from "@utils/api"; import * as React from "react"; import { Peer } from "@/interfaces/Peer"; -import { Route } from "@/interfaces/Route"; +import { useHasExitNodes } from "@/modules/exit-node/useHasExitNodes"; type Props = { peer: Peer; }; export const ExitNodePeerIndicator = ({ peer }: Props) => { - const { data: routes } = useFetchApi(`/routes`); - const isExitNode = routes?.some((route) => route?.peer === peer.id); + const hasExitNode = useHasExitNodes(peer); - return isExitNode ? ( + return hasExitNode ? ( - This peer is used as an exit node. All internet traffic will be routed - through this peer. + This peer has an exit node. Traffic from the configured distribution + groups will be routed through this peer.
} > diff --git a/src/modules/exit-node/useHasExitNodes.tsx b/src/modules/exit-node/useHasExitNodes.tsx new file mode 100644 index 00000000..baf651bc --- /dev/null +++ b/src/modules/exit-node/useHasExitNodes.tsx @@ -0,0 +1,10 @@ +import useFetchApi from "@utils/api"; +import { Peer } from "@/interfaces/Peer"; +import { Route } from "@/interfaces/Route"; + +export const useHasExitNodes = (peer?: Peer) => { + const { data: routes } = useFetchApi(`/routes`); + return peer + ? routes?.some((route) => route?.peer === peer.id) || false + : false; +}; diff --git a/src/modules/peer/PeerRoutesTable.tsx b/src/modules/peer/PeerRoutesTable.tsx index 0f3426d4..0c3bbe3c 100644 --- a/src/modules/peer/PeerRoutesTable.tsx +++ b/src/modules/peer/PeerRoutesTable.tsx @@ -14,6 +14,7 @@ import PeerRouteActiveCell from "@/modules/peer/PeerRouteActiveCell"; import PeerRouteNameCell from "@/modules/peer/PeerRouteNameCell"; import PeerRouteNetworkCell from "@/modules/peer/PeerRouteNetworkCell"; import usePeerRoutes from "@/modules/peer/usePeerRoutes"; +import RouteDistributionGroupsCell from "@/modules/routes/RouteDistributionGroupsCell"; type Props = { peer: Peer; @@ -35,6 +36,16 @@ export const RouteTableColumns: ColumnDef[] = [ }, cell: ({ row }) => , }, + { + id: "groups", + accessorFn: (r) => r.groups?.length, + header: ({ column }) => { + return ( + Distribution Groups + ); + }, + cell: ({ row }) => , + }, { id: "enabled", accessorKey: "enabled", diff --git a/src/modules/routes/RouteModal.tsx b/src/modules/routes/RouteModal.tsx index 6e6f8250..722fa888 100644 --- a/src/modules/routes/RouteModal.tsx +++ b/src/modules/routes/RouteModal.tsx @@ -65,21 +65,35 @@ type ModalProps = { onSuccess?: (route: Route) => void; peer?: Peer; exitNode?: boolean; + isFirstExitNode?: boolean; }; -export function RouteModalContent({ onSuccess, peer, exitNode }: ModalProps) { +export function RouteModalContent({ + onSuccess, + peer, + exitNode, + isFirstExitNode = false, +}: ModalProps) { const { createRoute } = useRoutes(); + const [tab, setTab] = useState("network"); - // General + /** + * Network Identifier, Description & Network Range + */ const [networkIdentifier, setNetworkIdentifier] = useState( - exitNode ? (peer ? `Exit Node (${peer.name})` : "Exit Node") : "", + exitNode + ? peer + ? `Exit Node (${ + peer.name.length > 25 + ? peer.name.substring(0, 25) + "..." + : peer.name + })` + : "Exit Node" + : "", ); const [description, setDescription] = useState(""); - - // Network const [networkRange, setNetworkRange] = useState(exitNode ? "0.0.0.0/0" : ""); const [routingPeer, setRoutingPeer] = useState(peer); - const [ routingPeerGroups, setRoutingPeerGroups, @@ -88,29 +102,23 @@ export function RouteModalContent({ onSuccess, peer, exitNode }: ModalProps) { initial: [], }); + /** + * Distribution Groups + */ const [groups, setGroups, { getGroupsToUpdate }] = useGroupHelper({ initial: [], }); - // Additional Settings + /** + * Additional Settings + */ const [enabled, setEnabled] = useState(true); const [metric, setMetric] = useState("9999"); const [masquerade, setMasquerade] = useState(true); - // Validate CIDR - const cidrError = useMemo(() => { - if (networkRange == "") return ""; - const validCIDR = cidr.isValidAddress(networkRange); - if (!validCIDR) return "Please enter a valid CIDR, e.g., 192.168.1.0/24"; - }, [networkRange]); - - // Refs to manage focus on tab change - const networkRangeRef = useRef(null); - const nameRef = useRef(null); - const [peerTab, setPeerTab] = useState("routing-peer"); - - // Create route - // TODO Refactor to avoid duplicate code + /** + * Create Route + */ const createRouteHandler = async () => { const g1 = getAllRoutingGroupsToUpdate(); const g2 = getGroupsToUpdate(); @@ -151,7 +159,25 @@ export function RouteModalContent({ onSuccess, peer, exitNode }: ModalProps) { ); }; - // Is button disabled + /** + * Refs to manage input focus on tab change + */ + const networkRangeRef = useRef(null); + const nameRef = useRef(null); + const [peerTab, setPeerTab] = useState("routing-peer"); + + /** + * Validate CIDR Range + */ + const cidrError = useMemo(() => { + if (networkRange == "") return ""; + const validCIDR = cidr.isValidAddress(networkRange); + if (!validCIDR) return "Please enter a valid CIDR, e.g., 192.168.1.0/24"; + }, [networkRange]); + + /** + * Allow to create route only when all fields are filled + */ const isDisabled = useMemo(() => { return ( networkIdentifier == "" || @@ -169,8 +195,6 @@ export function RouteModalContent({ onSuccess, peer, exitNode }: ModalProps) { groups, ]); - const [tab, setTab] = useState("network"); - return ( ) } - title={exitNode ? "Add Exit Node" : "Create New Route"} + title={ + exitNode + ? isFirstExitNode + ? "Setup Exit Node" + : "Add Exit Node" + : "Create New Route" + } + truncate={!!peer} description={ exitNode ? peer @@ -290,8 +321,11 @@ export function RouteModalContent({ onSuccess, peer, exitNode }: ModalProps) {
- Advertise this route to peers that belong to the following - groups + {exitNode + ? peer + ? `Route all internet traffic through this peer for the following groups` + : `Route all internet traffic through the peer(s) for the following groups` + : "Advertise this route to peers that belong to the following groups"}