From f68bff7e7266c4ebc1e6396aa33ba39083c84ec5 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 9 Jan 2025 10:18:37 +0100 Subject: [PATCH 01/26] Fix wrong ui state for routing peer modal in networks --- .../networks/routing-peers/NetworkRoutingPeerModal.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx index ea7a7f10..94dda20a 100644 --- a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx +++ b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx @@ -110,7 +110,7 @@ function RoutingPeerModalContent({ }); const [masquerade, setMasquerade] = useState( - router?.masquerade || true, + router ? router.masquerade : true, ); const [metric, setMetric] = useState( router?.metric ? router.metric.toString() : "9999", @@ -293,9 +293,7 @@ function RoutingPeerModalContent({ Learn more about Routing Peers From 67fd256e445b8c575204f57059674be8c4119eff Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 9 Jan 2025 14:36:35 +0100 Subject: [PATCH 02/26] Add confirmation dialog when blocking users --- src/app/(dashboard)/team/user/page.tsx | 1 + src/modules/users/table-cells/UserBlockCell.tsx | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/app/(dashboard)/team/user/page.tsx b/src/app/(dashboard)/team/user/page.tsx index 4fd68a73..44bc6983 100644 --- a/src/app/(dashboard)/team/user/page.tsx +++ b/src/app/(dashboard)/team/user/page.tsx @@ -316,6 +316,7 @@ function UserInformationCard({ user }: { user: User }) { <> {!user.is_current && user.role != Role.Owner && ( diff --git a/src/modules/users/table-cells/UserBlockCell.tsx b/src/modules/users/table-cells/UserBlockCell.tsx index bd8881ba..7f2dd2b0 100644 --- a/src/modules/users/table-cells/UserBlockCell.tsx +++ b/src/modules/users/table-cells/UserBlockCell.tsx @@ -3,6 +3,7 @@ import { ToggleSwitch } from "@components/ToggleSwitch"; import { useApiCall } from "@utils/api"; import React, { useMemo } from "react"; import { useSWRConfig } from "swr"; +import { useDialog } from "@/contexts/DialogProvider"; import { User } from "@/interfaces/User"; type Props = { @@ -12,6 +13,7 @@ type Props = { export default function UserBlockCell({ user, isUserPage = false }: Props) { const userRequest = useApiCall("/users"); const { mutate } = useSWRConfig(); + const { confirm } = useDialog(); const isChecked = useMemo(() => { return user.is_blocked; @@ -22,6 +24,18 @@ export default function UserBlockCell({ user, isUserPage = false }: Props) { const update = async (blocked: boolean) => { const name = user.name || "User"; + if (blocked) { + const choice = await confirm({ + title: `Block '${name}'?`, + description: + "This action will immediately revoke the user's access and disconnect all of their active peers.", + confirmText: "Block", + cancelText: "Cancel", + type: "danger", + }); + if (!choice) return; + } + notify({ title: blocked ? "User blocked" : "User unblocked", description: From 786efb7d130771cbe5bb8124b6b8b1f24c35c0db Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 9 Jan 2025 17:45:55 +0100 Subject: [PATCH 03/26] Keep peer sort order when switching pages --- src/modules/peers/PeersTable.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/peers/PeersTable.tsx b/src/modules/peers/PeersTable.tsx index 3f98c381..29f8a45e 100644 --- a/src/modules/peers/PeersTable.tsx +++ b/src/modules/peers/PeersTable.tsx @@ -63,7 +63,8 @@ const PeersTableColumns: ColumnDef[] = [ enableHiding: false, }, { - accessorKey: "name", + id: "name", + accessorFn: (peer) => `${peer?.name}${peer?.dns_label}`, header: ({ column }) => { return Name; }, @@ -94,6 +95,7 @@ const PeersTableColumns: ColumnDef[] = [ accessorFn: (peer) => (peer.user ? peer.user?.email : "Unknown"), }, { + id: "dns_label", accessorKey: "dns_label", header: ({ column }) => { return Address; @@ -193,6 +195,10 @@ export default function PeersTable({ peers, isLoading, headingTarget }: Props) { id: "connected", desc: true, }, + { + id: "name", + desc: false, + }, { id: "last_seen", desc: true, From 1e5824c19f4572cc532545b1fd91e2742a953d22 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 9 Jan 2025 18:12:36 +0100 Subject: [PATCH 04/26] Update sidebar navigation order and remove deprecation notice --- src/app/(dashboard)/network-routes/page.tsx | 5 +--- src/components/SidebarItem.tsx | 6 +++-- src/components/ui/GroupBadge.tsx | 12 ++------- src/components/ui/NewBadge.tsx | 16 ++++++++++++ .../networks/misc/NetworkNavigation.tsx | 25 +++++++++---------- 5 files changed, 35 insertions(+), 29 deletions(-) create mode 100644 src/components/ui/NewBadge.tsx diff --git a/src/app/(dashboard)/network-routes/page.tsx b/src/app/(dashboard)/network-routes/page.tsx index c4646cc8..e5b00981 100644 --- a/src/app/(dashboard)/network-routes/page.tsx +++ b/src/app/(dashboard)/network-routes/page.tsx @@ -14,7 +14,6 @@ import PeersProvider from "@/contexts/PeersProvider"; import RoutesProvider from "@/contexts/RoutesProvider"; import { Route } from "@/interfaces/Route"; import PageContainer from "@/layouts/PageContainer"; -import { NetworkRoutesDeprecationInfo } from "@/modules/networks/misc/NetworkRoutesDeprecationInfo"; import useGroupedRoutes from "@/modules/route-group/useGroupedRoutes"; const NetworkRoutesTable = lazy( @@ -40,9 +39,7 @@ export default function NetworkRoutes() { icon={} /> -

- Network Routes -

+

Network Routes

Network routes allow you to access other networks like LANs and VPCs without installing NetBird on every resource. diff --git a/src/components/SidebarItem.tsx b/src/components/SidebarItem.tsx index 1836b61a..067d40c2 100644 --- a/src/components/SidebarItem.tsx +++ b/src/components/SidebarItem.tsx @@ -60,10 +60,12 @@ export default function SidebarItem({
  • - - [] = [ { id: "id", - accessorKey: "id", + accessorKey: "name", header: ({ column }) => { return Resource; }, @@ -42,7 +47,10 @@ const NetworkResourceColumns: ColumnDef[] = [ }, { id: "groups", - accessorKey: "id", + accessorFn: (resource) => { + let groups = resource?.groups as Group[]; + return groups.map((group) => group.name).join(", "); + }, header: ({ column }) => { return Groups; }, @@ -76,6 +84,7 @@ export default function ResourcesTable({ headingTarget, }: Props) { const [sorting, setSorting] = useState([]); + const { openResourceModal, network } = useNetworksContext(); return ( <> @@ -86,14 +95,14 @@ export default function ResourcesTable({ sorting={sorting} setSorting={setSorting} minimal={true} - showSearchAndFilters={false} + showSearchAndFilters={true} inset={false} tableClassName={"mt-0"} - text={"Peers"} + text={"Resources"} columns={NetworkResourceColumns} keepStateInLocalStorage={false} data={resources} - searchPlaceholder={"Search by name, IP, owner or group..."} + searchPlaceholder={"Search by name, address or group..."} isLoading={isLoading} getStartedCard={ + rightSide={() => ( + <> + + + )} + > + {(table) => ( + <> + + + )} + ); } From 5672b41d3c072c4f8fcb257c4e4b94dd1f3e9b1d Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Mon, 13 Jan 2025 10:37:35 +0100 Subject: [PATCH 10/26] Switch networks flow to create first resources and then add routers --- src/modules/networks/NetworkProvider.tsx | 8 ++--- src/modules/networks/table/NetworksTable.tsx | 35 +++++--------------- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/src/modules/networks/NetworkProvider.tsx b/src/modules/networks/NetworkProvider.tsx index d214d05f..3c529808 100644 --- a/src/modules/networks/NetworkProvider.tsx +++ b/src/modules/networks/NetworkProvider.tsx @@ -232,7 +232,7 @@ export const NetworkProvider = ({ children, network }: Props) => { network={currentNetwork} onCreated={async (network) => { mutate("/networks"); - await askForRoutingPeer(network); + await askForResource(network); }} onUpdated={() => { mutate("/networks"); @@ -250,13 +250,15 @@ export const NetworkProvider = ({ children, network }: Props) => { initialDestinationGroups={policyDefaultSettings?.destinationGroups} initialName={policyDefaultSettings?.name} initialDescription={policyDefaultSettings?.description} - onSuccess={(p) => { + onSuccess={async (p) => { setPolicyModal(false); setPolicyDefaultSettings(undefined); mutate("/networks"); if (network) { mutate(`/networks/${network.id}/resources`); mutate(`/networks/${network.id}`); + } else { + currentNetwork && (await askForRoutingPeer(currentNetwork)); } }} /> @@ -275,8 +277,6 @@ export const NetworkProvider = ({ children, network }: Props) => { if (network) { mutate(`/networks/${currentNetwork.id}/routers`); mutate(`/networks/${network.id}`); - } else { - await askForResource(currentNetwork); } }} onUpdated={async () => { diff --git a/src/modules/networks/table/NetworksTable.tsx b/src/modules/networks/table/NetworksTable.tsx index dda7f662..f3177a65 100644 --- a/src/modules/networks/table/NetworksTable.tsx +++ b/src/modules/networks/table/NetworksTable.tsx @@ -13,7 +13,6 @@ import { usePathname } from "next/navigation"; import React from "react"; import { useSWRConfig } from "swr"; import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon"; -import { useDialog } from "@/contexts/DialogProvider"; import { useLocalStorage } from "@/hooks/useLocalStorage"; import { Network } from "@/interfaces/Network"; import { @@ -38,14 +37,6 @@ export const NetworkTableColumns: ColumnDef[] = [ { accessorKey: "description", }, - { - accessorKey: "routers", - accessorFn: (network) => network?.routers?.length, - header: ({ column }) => { - return Routing Peers; - }, - cell: ({ row }) => , - }, { accessorKey: "resources", accessorFn: (network) => network?.resources?.length, @@ -62,6 +53,14 @@ export const NetworkTableColumns: ColumnDef[] = [ }, cell: ({ row }) => , }, + { + accessorKey: "routers", + accessorFn: (network) => network?.routers?.length, + header: ({ column }) => { + return Routing Peers; + }, + cell: ({ row }) => , + }, { accessorKey: "id", header: "", @@ -94,20 +93,6 @@ export default function NetworksTable({ ], ); - const { confirm } = useDialog(); - - const showConfirm = async () => { - const choice = await confirm({ - title: `Do you want to add a resource to 'Office Network' now?`, - description: - "Peers will be able to access your network resources once you add them.", - confirmText: "Add Resource", - cancelText: "Later", - type: "default", - }); - if (!choice) return; - }; - return ( Learn more about Networks From a541b73ef6c056392bb66785487618e239c6e903 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Mon, 13 Jan 2025 11:09:44 +0100 Subject: [PATCH 11/26] Add enabled toggle to routing peers --- src/interfaces/Network.ts | 2 + .../routing-peers/NetworkRoutingPeerModal.tsx | 19 ++++++++ .../NetworkRoutingPeersTable.tsx | 9 ++++ .../routing-peers/RoutingPeersEnabledCell.tsx | 48 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 src/modules/networks/routing-peers/RoutingPeersEnabledCell.tsx diff --git a/src/interfaces/Network.ts b/src/interfaces/Network.ts index 1e9f37aa..e7cd5601 100644 --- a/src/interfaces/Network.ts +++ b/src/interfaces/Network.ts @@ -16,6 +16,7 @@ export interface NetworkRouter { peer_groups?: string[]; metric: number; masquerade: boolean; + enabled: boolean; } export interface NetworkResource { @@ -25,4 +26,5 @@ export interface NetworkResource { address: string; groups?: string[] | Group[]; type?: "domain" | "host" | "subnet"; + enabled: boolean; } diff --git a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx index 94dda20a..612a1c8b 100644 --- a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx +++ b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx @@ -28,6 +28,7 @@ import { FolderGit2, MonitorSmartphoneIcon, PlusCircle, + Power, Settings2, Share2Icon, VenetianMask, @@ -112,6 +113,9 @@ function RoutingPeerModalContent({ const [masquerade, setMasquerade] = useState( router ? router.masquerade : true, ); + const [enabled, setEnabled] = useState( + router ? router.enabled : true, + ); const [metric, setMetric] = useState( router?.metric ? router.metric.toString() : "9999", ); @@ -137,6 +141,7 @@ function RoutingPeerModalContent({ ? createdGroups.map((g) => g.id) : undefined, metric: parseInt(metric), + enabled, masquerade, }).then((r) => { onCreated?.(r); @@ -165,6 +170,7 @@ function RoutingPeerModalContent({ ? createdGroups.map((g) => g.id) : undefined, metric: parseInt(metric), + enabled, masquerade, }).then((r) => { onUpdated?.(r); @@ -245,6 +251,19 @@ function RoutingPeerModalContent({
    + + + Enable Routing Peer + + } + helpText={ + "Use this switch to enable or disable the routing peer." + } + /> [] = [ sortingFn: "text", cell: ({ row }) => , }, + { + id: "enabled", + accessorKey: "enabled", + header: ({ column }) => { + return Active; + }, + cell: ({ row }) => , + }, { id: "metric", accessorKey: "metric", diff --git a/src/modules/networks/routing-peers/RoutingPeersEnabledCell.tsx b/src/modules/networks/routing-peers/RoutingPeersEnabledCell.tsx new file mode 100644 index 00000000..debd5925 --- /dev/null +++ b/src/modules/networks/routing-peers/RoutingPeersEnabledCell.tsx @@ -0,0 +1,48 @@ +import { notify } from "@components/Notification"; +import { ToggleSwitch } from "@components/ToggleSwitch"; +import { useApiCall } from "@utils/api"; +import * as React from "react"; +import { useMemo } from "react"; +import { useSWRConfig } from "swr"; +import { NetworkRouter } from "@/interfaces/Network"; +import { useNetworksContext } from "@/modules/networks/NetworkProvider"; + +type Props = { + router: NetworkRouter; +}; +export const RoutingPeersEnabledCell = ({ router }: Props) => { + const { mutate } = useSWRConfig(); + const { network } = useNetworksContext(); + + const update = useApiCall( + `/networks/${network?.id}/routers/${router?.id}`, + ).put; + + const toggle = async (enabled: boolean) => { + notify({ + title: "Network Routing Peer", + description: `Routing peer is now ${enabled ? "enabled" : "disabled"}`, + loadingMessage: "Updating routing peer...", + promise: update({ + ...router, + enabled, + }).then(() => { + mutate(`/networks/${network?.id}/routers`); + }), + }); + }; + + const isChecked = useMemo(() => { + return router.enabled; + }, [router]); + + return ( +
    + toggle(!isChecked)} + /> +
    + ); +}; From 92eef5f2aba6d28b118a40efb1e23a98894e273a Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Mon, 13 Jan 2025 12:30:30 +0100 Subject: [PATCH 12/26] Add enabled toggle to network resources --- .../resources/NetworkResourceModal.tsx | 26 ++++++- .../networks/resources/ResourceActionCell.tsx | 68 ++++++++++++------- .../resources/ResourceEnabledCell.tsx | 58 ++++++++++++++++ .../networks/resources/ResourceGroupCell.tsx | 13 +++- .../networks/resources/ResourceNameCell.tsx | 28 ++++++-- .../networks/resources/ResourcePolicyCell.tsx | 2 +- .../networks/resources/ResourcesTable.tsx | 9 +++ .../NetworkRoutingPeersTable.tsx | 6 +- .../routing-peers/RoutingPeersActionCell.tsx | 68 ++++++++++++------- src/modules/routes/RouteMetricCell.tsx | 8 ++- 10 files changed, 228 insertions(+), 58 deletions(-) create mode 100644 src/modules/networks/resources/ResourceEnabledCell.tsx diff --git a/src/modules/networks/resources/NetworkResourceModal.tsx b/src/modules/networks/resources/NetworkResourceModal.tsx index 1099126a..08c86f9b 100644 --- a/src/modules/networks/resources/NetworkResourceModal.tsx +++ b/src/modules/networks/resources/NetworkResourceModal.tsx @@ -1,6 +1,7 @@ "use client"; import Button from "@components/Button"; +import FancyToggleSwitch from "@components/FancyToggleSwitch"; import HelpText from "@components/HelpText"; import InlineLink from "@components/InlineLink"; import { Input } from "@components/Input"; @@ -17,7 +18,12 @@ import Paragraph from "@components/Paragraph"; import { PeerGroupSelector } from "@components/PeerGroupSelector"; import Separator from "@components/Separator"; import { useApiCall } from "@utils/api"; -import { ExternalLinkIcon, PlusCircle, WorkflowIcon } from "lucide-react"; +import { + ExternalLinkIcon, + PlusCircle, + Power, + WorkflowIcon, +} from "lucide-react"; import React, { useMemo, useState } from "react"; import { Network, NetworkResource } from "@/interfaces/Network"; import useGroupHelper from "@/modules/groups/useGroupHelper"; @@ -79,6 +85,9 @@ export function ResourceModalContent({ const [groups, setGroups, { save: saveGroups }] = useGroupHelper({ initial: resource?.groups || [], }); + const [enabled, setEnabled] = useState( + resource ? resource.enabled : true, + ); const createResource = async () => { const savedGroups = await saveGroups(); @@ -91,6 +100,7 @@ export function ResourceModalContent({ description, address, groups: savedGroups.map((g) => g.id), + enabled, }).then((r) => { onCreated?.(r); }), @@ -108,6 +118,7 @@ export function ResourceModalContent({ description, address, groups: savedGroups.map((g) => g.id), + enabled, }).then((r) => { onUpdated?.(r); }), @@ -156,6 +167,19 @@ export function ResourceModalContent({
    +
    + + + Enable Resource + + } + helpText={"Use this switch to enable or disable the resource."} + /> +
    diff --git a/src/modules/networks/resources/ResourceActionCell.tsx b/src/modules/networks/resources/ResourceActionCell.tsx index 39b5f944..86bad3ce 100644 --- a/src/modules/networks/resources/ResourceActionCell.tsx +++ b/src/modules/networks/resources/ResourceActionCell.tsx @@ -1,5 +1,11 @@ import Button from "@components/Button"; -import { SquarePenIcon, Trash2 } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@components/DropdownMenu"; +import { MoreVertical, SquarePenIcon, Trash2 } from "lucide-react"; import * as React from "react"; import { NetworkResource } from "@/interfaces/Network"; import { useNetworksContext } from "@/modules/networks/NetworkProvider"; @@ -12,28 +18,44 @@ export const ResourceActionCell = ({ resource }: Props) => { return (
    - - + + { + e.stopPropagation(); + e.preventDefault(); + }} + > + + + + { + if (!network) return; + openResourceModal(network, resource); + }} + > +
    + + Edit +
    +
    + { + if (!network) return; + deleteResource(network, resource); + }} + variant={"danger"} + > +
    + + Remove +
    +
    +
    +
    ); }; diff --git a/src/modules/networks/resources/ResourceEnabledCell.tsx b/src/modules/networks/resources/ResourceEnabledCell.tsx new file mode 100644 index 00000000..dd24c119 --- /dev/null +++ b/src/modules/networks/resources/ResourceEnabledCell.tsx @@ -0,0 +1,58 @@ +import { notify } from "@components/Notification"; +import { ToggleSwitch } from "@components/ToggleSwitch"; +import { useApiCall } from "@utils/api"; +import * as React from "react"; +import { useMemo } from "react"; +import { useSWRConfig } from "swr"; +import { Group } from "@/interfaces/Group"; +import { NetworkResource } from "@/interfaces/Network"; +import { useNetworksContext } from "@/modules/networks/NetworkProvider"; + +type Props = { + resource: NetworkResource; +}; +export const ResourceEnabledCell = ({ resource }: Props) => { + const { mutate } = useSWRConfig(); + const { network } = useNetworksContext(); + + const update = useApiCall( + `/networks/${network?.id}/resources/${resource?.id}`, + ).put; + + const toggle = async (enabled: boolean) => { + notify({ + title: `Update Resource`, + description: `'${resource?.name}' is now ${ + enabled ? "enabled" : "disabled" + }`, + loadingMessage: "Updating resource...", + duration: 1200, + promise: update({ + ...resource, + groups: resource.groups + ?.map((g) => { + let group = g as Group; + return group.id; + }) + .filter((g) => g !== undefined), + enabled, + }).then(() => { + mutate(`/networks/${network?.id}/resources`); + }), + }); + }; + + const isChecked = useMemo(() => { + return resource.enabled; + }, [resource]); + + return ( +
    + toggle(!isChecked)} + /> +
    + ); +}; diff --git a/src/modules/networks/resources/ResourceGroupCell.tsx b/src/modules/networks/resources/ResourceGroupCell.tsx index 1b6c1d8b..efcdf2b5 100644 --- a/src/modules/networks/resources/ResourceGroupCell.tsx +++ b/src/modules/networks/resources/ResourceGroupCell.tsx @@ -2,14 +2,23 @@ import MultipleGroups from "@components/ui/MultipleGroups"; import * as React from "react"; import { Group } from "@/interfaces/Group"; import { NetworkResource } from "@/interfaces/Network"; +import { useNetworksContext } from "@/modules/networks/NetworkProvider"; type Props = { resource?: NetworkResource; }; export const ResourceGroupCell = ({ resource }: Props) => { + const { network, openResourceModal } = useNetworksContext(); + return ( -
    +
    + ); }; diff --git a/src/modules/networks/resources/ResourceNameCell.tsx b/src/modules/networks/resources/ResourceNameCell.tsx index 17f2e8e7..90286b76 100644 --- a/src/modules/networks/resources/ResourceNameCell.tsx +++ b/src/modules/networks/resources/ResourceNameCell.tsx @@ -1,32 +1,52 @@ import DescriptionWithTooltip from "@components/ui/DescriptionWithTooltip"; +import TextWithTooltip from "@components/ui/TextWithTooltip"; import { cn } from "@utils/helpers"; import { GlobeIcon, NetworkIcon, WorkflowIcon } from "lucide-react"; import React from "react"; import { NetworkResource } from "@/interfaces/Network"; +import { useNetworksContext } from "@/modules/networks/NetworkProvider"; type Props = { resource: NetworkResource; }; export default function ResourceNameCell({ resource }: Readonly) { + const { network, openResourceModal } = useNetworksContext(); + return ( -
    + ); } diff --git a/src/modules/networks/resources/ResourcePolicyCell.tsx b/src/modules/networks/resources/ResourcePolicyCell.tsx index f0f0d2ff..9551b493 100644 --- a/src/modules/networks/resources/ResourcePolicyCell.tsx +++ b/src/modules/networks/resources/ResourcePolicyCell.tsx @@ -89,7 +89,7 @@ export const ResourcePolicyCell = ({ resource }: Props) => { - + + { + e.stopPropagation(); + e.preventDefault(); + }} + > + + + + { + if (!network) return; + openAddRoutingPeerModal(network, router); + }} + > +
    + + Edit +
    +
    + { + if (!network) return; + deleteRouter(network, router); + }} + variant={"danger"} + > +
    + + Remove +
    +
    +
    +
    ); }; diff --git a/src/modules/routes/RouteMetricCell.tsx b/src/modules/routes/RouteMetricCell.tsx index 6b72677c..a805de93 100644 --- a/src/modules/routes/RouteMetricCell.tsx +++ b/src/modules/routes/RouteMetricCell.tsx @@ -3,11 +3,15 @@ import { ArrowUpDown, InfoIcon } from "lucide-react"; type Props = { metric?: number; + useHoverStyle?: boolean; }; -export default function RouteMetricCell({ metric }: Props) { +export default function RouteMetricCell({ + metric, + useHoverStyle = true, +}: Readonly) { return ( From 449344f8a8dbb525455d850c67e8b712674759b2 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Mon, 13 Jan 2025 14:56:05 +0100 Subject: [PATCH 13/26] Add resource group modal and adjust tables --- src/modules/networks/NetworkProvider.tsx | 36 ++++++ .../networks/resources/ResourceActionCell.tsx | 2 +- .../networks/resources/ResourceGroupCell.tsx | 4 +- .../networks/resources/ResourceGroupModal.tsx | 121 ++++++++++++++++++ .../networks/resources/ResourcesTable.tsx | 100 +++++++-------- .../routing-peers/NetworkRoutingPeerName.tsx | 12 +- .../NetworkRoutingPeersTable.tsx | 31 ++++- .../routing-peers/RoutingPeersActionCell.tsx | 2 +- 8 files changed, 240 insertions(+), 68 deletions(-) create mode 100644 src/modules/networks/resources/ResourceGroupModal.tsx diff --git a/src/modules/networks/NetworkProvider.tsx b/src/modules/networks/NetworkProvider.tsx index 3c529808..171f5a33 100644 --- a/src/modules/networks/NetworkProvider.tsx +++ b/src/modules/networks/NetworkProvider.tsx @@ -10,6 +10,7 @@ import { Network, NetworkResource, NetworkRouter } from "@/interfaces/Network"; import { AccessControlModalContent } from "@/modules/access-control/AccessControlModal"; import NetworkModal from "@/modules/networks/NetworkModal"; import NetworkResourceModal from "@/modules/networks/resources/NetworkResourceModal"; +import { ResourceGroupModal } from "@/modules/networks/resources/ResourceGroupModal"; import NetworkRoutingPeerModal from "@/modules/networks/routing-peers/NetworkRoutingPeerModal"; type Props = { @@ -23,6 +24,10 @@ const NetworksContext = React.createContext( openEditNetworkModal: (network: Network) => void; openCreateNetworkModal: () => void; openResourceModal: (network: Network, resource?: NetworkResource) => void; + openResourceGroupModal: ( + network: Network, + resource?: NetworkResource, + ) => void; openPolicyModal: (network?: Network, resource?: NetworkResource) => void; deleteNetwork: (network: Network) => void; deleteResource: (network: Network, resource: NetworkResource) => void; @@ -49,6 +54,7 @@ export const NetworkProvider = ({ children, network }: Props) => { const [routingPeerModal, setRoutingPeerModal] = useState(false); const [networkModal, setNetworkModal] = useState(false); const [resourceModal, setResourceModal] = useState(false); + const [resourceGroupModal, setResourceGroupModal] = useState(false); const [policyModal, setPolicyModal] = useState(false); const openAddRoutingPeerModal = ( @@ -76,6 +82,15 @@ export const NetworkProvider = ({ children, network }: Props) => { setResourceModal(true); }; + const openResourceGroupModal = ( + network: Network, + resource?: NetworkResource, + ) => { + setCurrentNetwork(network); + resource && setCurrentResource(resource); + setResourceGroupModal(true); + }; + const openPolicyModal = (network?: Network, resource?: NetworkResource) => { setPolicyDefaultSettings({ destinationGroups: resource?.groups, @@ -217,6 +232,7 @@ export const NetworkProvider = ({ children, network }: Props) => { openEditNetworkModal, openCreateNetworkModal, openResourceModal, + openResourceGroupModal, openPolicyModal, deleteNetwork, deleteResource, @@ -294,6 +310,26 @@ export const NetworkProvider = ({ children, network }: Props) => { setRoutingPeerModal(state); }} /> + + { + setCurrentResource(undefined); + setResourceGroupModal(state); + }} + onUpdated={() => { + setResourceGroupModal(false); + setCurrentResource(undefined); + mutate("/groups"); + if (network) { + mutate(`/networks/${network.id}/resources`); + mutate(`/networks/${network.id}`); + } + }} + /> + { const { deleteResource, network, openResourceModal } = useNetworksContext(); return ( -
    +
    { - const { network, openResourceModal } = useNetworksContext(); + const { network, openResourceGroupModal } = useNetworksContext(); return ( + + + +
    + + + ); +}; diff --git a/src/modules/networks/resources/ResourcesTable.tsx b/src/modules/networks/resources/ResourcesTable.tsx index 547a6187..478845ad 100644 --- a/src/modules/networks/resources/ResourcesTable.tsx +++ b/src/modules/networks/resources/ResourcesTable.tsx @@ -91,62 +91,56 @@ export default function ResourcesTable({ resources, isLoading, headingTarget, -}: Props) { +}: Readonly) { const [sorting, setSorting] = useState([]); const { openResourceModal, network } = useNetworksContext(); return ( - <> - } - /> - } - columnVisibility={{}} - paginationPaddingClassName={"px-0 pt-8"} - rightSide={() => ( - <> - - - )} - > - {(table) => ( - <> - - - )} - - + } + /> + } + columnVisibility={{}} + paginationPaddingClassName={"px-0 pt-8"} + rightSide={() => ( + + )} + > + {(table) => ( + + )} + ); } diff --git a/src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx b/src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx index 03511e2c..092f5064 100644 --- a/src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx +++ b/src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx @@ -44,13 +44,11 @@ export const NetworkRoutingPeerName = ({ router }: Props) => { if (routingPeerGroup) { return ( - <> -
    - - - {routingPeerGroup.peers_count} Peer(s) -
    - +
    + + + {routingPeerGroup.peers_count} Peer(s) +
    ); } }; diff --git a/src/modules/networks/routing-peers/NetworkRoutingPeersTable.tsx b/src/modules/networks/routing-peers/NetworkRoutingPeersTable.tsx index 5c138b76..9e801ad9 100644 --- a/src/modules/networks/routing-peers/NetworkRoutingPeersTable.tsx +++ b/src/modules/networks/routing-peers/NetworkRoutingPeersTable.tsx @@ -1,12 +1,16 @@ +import Button from "@components/Button"; import Card from "@components/Card"; import { DataTable } from "@components/table/DataTable"; import DataTableHeader from "@components/table/DataTableHeader"; +import { DataTableRowsPerPage } from "@components/table/DataTableRowsPerPage"; import NoResults from "@components/ui/NoResults"; +import { IconCirclePlus } from "@tabler/icons-react"; import { ColumnDef, SortingState } from "@tanstack/react-table"; import * as React from "react"; import { useState } from "react"; import PeerIcon from "@/assets/icons/PeerIcon"; import { NetworkRouter } from "@/interfaces/Network"; +import { useNetworksContext } from "@/modules/networks/NetworkProvider"; import { NetworkRoutingPeerName } from "@/modules/networks/routing-peers/NetworkRoutingPeerName"; import { RoutingPeersActionCell } from "@/modules/networks/routing-peers/RoutingPeersActionCell"; import { RoutingPeersEnabledCell } from "@/modules/networks/routing-peers/RoutingPeersEnabledCell"; @@ -70,6 +74,8 @@ export default function NetworkRoutingPeersTable({ isLoading, headingTarget, }: Readonly) { + const { openAddRoutingPeerModal, network } = useNetworksContext(); + const [sorting, setSorting] = useState([ { id: "metric", @@ -80,7 +86,7 @@ export default function NetworkRoutingPeersTable({ return ( + rightSide={() => ( + + )} + > + {(table) => ( + + )} + ); } diff --git a/src/modules/networks/routing-peers/RoutingPeersActionCell.tsx b/src/modules/networks/routing-peers/RoutingPeersActionCell.tsx index cb53fea7..3c64415c 100644 --- a/src/modules/networks/routing-peers/RoutingPeersActionCell.tsx +++ b/src/modules/networks/routing-peers/RoutingPeersActionCell.tsx @@ -18,7 +18,7 @@ export const RoutingPeersActionCell = ({ router }: Props) => { useNetworksContext(); return ( -
    +
    Date: Tue, 14 Jan 2025 13:19:39 +0200 Subject: [PATCH 14/26] Clarify networks --- src/app/(dashboard)/networks/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(dashboard)/networks/page.tsx b/src/app/(dashboard)/networks/page.tsx index bdda9574..07683348 100644 --- a/src/app/(dashboard)/networks/page.tsx +++ b/src/app/(dashboard)/networks/page.tsx @@ -31,8 +31,8 @@ export default function Networks() {

    Networks

    - Networks allow you to access other resources in LANs and VPCs without - installing NetBird on every device. + Networks allow you to access internal resources in LANs and VPCs without + installing NetBird on every machine. Learn more about From 2b7b5875d28b1a8d29e4aac5ba515af7adb9af00 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Tue, 14 Jan 2025 16:18:25 +0100 Subject: [PATCH 15/26] Fix not properly aligned horizontal scroll bar --- src/components/ScrollArea.tsx | 51 +++++++++++------------------------ 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/src/components/ScrollArea.tsx b/src/components/ScrollArea.tsx index 1f5551aa..4f8c6925 100644 --- a/src/components/ScrollArea.tsx +++ b/src/components/ScrollArea.tsx @@ -1,5 +1,3 @@ -"use client"; - import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; import { cn } from "@utils/helpers"; import * as React from "react"; @@ -15,46 +13,31 @@ const ScrollArea = React.forwardRef< >(({ className, children, withoutViewport = false, ...props }, ref) => ( {withoutViewport ? ( children ) : ( - - {children} - + {children} )} - + + )); ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; -type AdditionalScrollAreaViewportProps = { - disableOverflowY?: boolean; -}; - const ScrollAreaViewport = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & - AdditionalScrollAreaViewportProps ->(({ disableOverflowY = true, ...props }, ref) => { - return ( - - ); -}); + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); ScrollAreaViewport.displayName = ScrollAreaPrimitive.Viewport.displayName; const ScrollBar = React.forwardRef< @@ -63,14 +46,11 @@ const ScrollBar = React.forwardRef< >(({ className, orientation = "vertical", ...props }, ref) => ( From 57e71a330e5cbaf181dc225ee86283a1eeb37b17 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Tue, 14 Jan 2025 16:19:02 +0100 Subject: [PATCH 16/26] Add option to install netbird after creating a setup key --- src/modules/setup-keys/SetupKeyModal.tsx | 19 ++- src/modules/setup-netbird-modal/DockerTab.tsx | 11 +- src/modules/setup-netbird-modal/LinuxTab.tsx | 21 +++- src/modules/setup-netbird-modal/MacOSTab.tsx | 52 +++++--- .../setup-netbird-modal/SetupModal.tsx | 119 ++++++++++++------ .../setup-netbird-modal/WindowsTab.tsx | 41 ++++-- 6 files changed, 191 insertions(+), 72 deletions(-) diff --git a/src/modules/setup-keys/SetupKeyModal.tsx b/src/modules/setup-keys/SetupKeyModal.tsx index 3192cff1..28f9cdbf 100644 --- a/src/modules/setup-keys/SetupKeyModal.tsx +++ b/src/modules/setup-keys/SetupKeyModal.tsx @@ -23,7 +23,7 @@ import { cn } from "@utils/helpers"; import { trim } from "lodash"; import { AlarmClock, - CopyIcon, + DownloadIcon, ExternalLinkIcon, MonitorSmartphoneIcon, PlusCircle, @@ -35,6 +35,7 @@ import SetupKeysIcon from "@/assets/icons/SetupKeysIcon"; import useCopyToClipboard from "@/hooks/useCopyToClipboard"; import { SetupKey } from "@/interfaces/SetupKey"; import useGroupHelper from "@/modules/groups/useGroupHelper"; +import SetupModal from "@/modules/setup-netbird-modal/SetupModal"; type Props = { children?: React.ReactNode; @@ -49,6 +50,7 @@ export default function SetupKeyModal({ }: Readonly) { const [successModal, setSuccessModal] = useState(false); const [setupKey, setSetupKey] = useState(); + const [installModal, setInstallModal] = useState(false); const [, copy] = useCopyToClipboard(setupKey?.key); const handleSuccess = (setupKey: SetupKey) => { @@ -62,6 +64,15 @@ export default function SetupKeyModal({ {children && {children}} + + + + + { @@ -118,10 +129,10 @@ export default function SetupKeyModal({ variant={"primary"} className={"w-full"} data-cy={"setup-key-copy"} - onClick={() => copy(copyMessage)} + onClick={() => setInstallModal(true)} > - - Copy to clipboard + + Install NetBird
    diff --git a/src/modules/setup-netbird-modal/DockerTab.tsx b/src/modules/setup-netbird-modal/DockerTab.tsx index eb35b000..546f1020 100644 --- a/src/modules/setup-netbird-modal/DockerTab.tsx +++ b/src/modules/setup-netbird-modal/DockerTab.tsx @@ -10,7 +10,11 @@ import Link from "next/link"; import React from "react"; import { OperatingSystem } from "@/interfaces/OperatingSystem"; -export default function DockerTab() { +type Props = { + setupKey?: string; +}; + +export default function DockerTab({ setupKey }: Readonly) { return ( @@ -42,7 +46,10 @@ export default function DockerTab() { {" "} -e NB_SETUP_KEY= - SETUP_KEY \ + + {setupKey ?? "SETUP_KEY"} + {" "} + \ -v netbird-client:/etc/netbird \ {GRPC_API_ORIGIN && ( diff --git a/src/modules/setup-netbird-modal/LinuxTab.tsx b/src/modules/setup-netbird-modal/LinuxTab.tsx index 9fcadaae..51fc5034 100644 --- a/src/modules/setup-netbird-modal/LinuxTab.tsx +++ b/src/modules/setup-netbird-modal/LinuxTab.tsx @@ -13,8 +13,13 @@ import { getNetBirdUpCommand } from "@utils/netbird"; import { TerminalSquareIcon } from "lucide-react"; import React from "react"; import { OperatingSystem } from "@/interfaces/OperatingSystem"; +import { SetupKeyParameter } from "@/modules/setup-netbird-modal/SetupModal"; -export default function LinuxTab() { +type Props = { + setupKey?: string; +}; + +export default function LinuxTab({ setupKey }: Readonly) { return ( @@ -27,9 +32,12 @@ export default function LinuxTab() { curl -fsSL https://pkgs.netbird.io/install.sh | sh -

    Run NetBird and log in the browser

    +

    Run NetBird {!setupKey && "and log in the browser"}

    - {getNetBirdUpCommand()} + + {getNetBirdUpCommand()} + +
    @@ -78,9 +86,12 @@ export default function LinuxTab() { -

    Run NetBird and log in the browser

    +

    Run NetBird {!setupKey && "and log in the browser"}

    - {getNetBirdUpCommand()} + + {getNetBirdUpCommand()} + +
    diff --git a/src/modules/setup-netbird-modal/MacOSTab.tsx b/src/modules/setup-netbird-modal/MacOSTab.tsx index a9b5964c..7e4f1525 100644 --- a/src/modules/setup-netbird-modal/MacOSTab.tsx +++ b/src/modules/setup-netbird-modal/MacOSTab.tsx @@ -23,8 +23,12 @@ import { import Link from "next/link"; import React from "react"; import { OperatingSystem } from "@/interfaces/OperatingSystem"; +import { SetupKeyParameter } from "@/modules/setup-netbird-modal/SetupModal"; -export default function MacOSTab() { +type Props = { + setupKey?: string; +}; +export default function MacOSTab({ setupKey }: Readonly) { return ( @@ -98,15 +102,29 @@ export default function MacOSTab() { )} - -

    - {/* eslint-disable-next-line react/no-unescaped-entities */} - Click on "Connect" from the NetBird icon in your system tray -

    -
    - -

    Sign up using your email address

    -
    + {setupKey ? ( + +

    Open Terminal and run NetBird

    + + + {getNetBirdUpCommand()} + + + +
    + ) : ( + <> + +

    + {/* eslint-disable-next-line react/no-unescaped-entities */} + Click on "Connect" from the NetBird icon in your system tray +

    +
    + +

    Sign up using your email address

    +
    + + )}
    @@ -125,9 +143,12 @@ export default function MacOSTab() { -

    Run NetBird and log in the browser

    +

    Run NetBird {!setupKey && "and log in the browser"}

    - {getNetBirdUpCommand()} + + {getNetBirdUpCommand()} + +
    @@ -179,9 +200,12 @@ export default function MacOSTab() { -

    Run NetBird and log in the browser

    +

    Run NetBird {!setupKey && "and log in the browser"}

    - {getNetBirdUpCommand()} + + {getNetBirdUpCommand()} + +
    diff --git a/src/modules/setup-netbird-modal/SetupModal.tsx b/src/modules/setup-netbird-modal/SetupModal.tsx index e7a4d591..491ac971 100644 --- a/src/modules/setup-netbird-modal/SetupModal.tsx +++ b/src/modules/setup-netbird-modal/SetupModal.tsx @@ -5,6 +5,7 @@ import { ModalContent, ModalFooter } from "@components/modal/Modal"; import Paragraph from "@components/Paragraph"; import SmallParagraph from "@components/SmallParagraph"; import { Tabs, TabsList, TabsTrigger } from "@components/Tabs"; +import { cn } from "@utils/helpers"; import { ExternalLinkIcon } from "lucide-react"; import { usePathname } from "next/navigation"; import React from "react"; @@ -31,27 +32,36 @@ type OidcUserInfo = { type Props = { showClose?: boolean; user?: OidcUserInfo; + setupKey?: string; }; -export default function SetupModal({ showClose = true, user }: Props) { +export default function SetupModal({ + showClose = true, + user, + setupKey, +}: Readonly) { return ( - + ); } +type SetupModalContentProps = { + user?: OidcUserInfo; + header?: boolean; + footer?: boolean; + tabAlignment?: "center" | "start" | "end"; + setupKey?: string; +}; + export function SetupModalContent({ user, header = true, footer = true, tabAlignment = "center", -}: { - user?: OidcUserInfo; - header?: boolean; - footer?: boolean; - tabAlignment?: "center" | "start" | "end"; -}) { + setupKey, +}: Readonly) { const os = useOperatingSystem(); const [isFirstRun] = useLocalStorage("netbird-first-run", true); const pathname = usePathname(); @@ -60,24 +70,33 @@ export function SetupModalContent({ return ( <> {header && ( -
    -

    +
    +

    {isFirstRun && !isInstallPage ? ( <> Hello {user?.given_name || "there"}! 👋
    {`It's time to add your first device.`} ) : ( - <>Install NetBird + <>Install NetBird with Setup Key )}

    - - To get started, install NetBird and log in with your email account. + + {setupKey + ? "To get started, install and run NetBird with your recently created setup key as a parameter." + : "To get started, install NetBird and log in with your email account."}
    )} - + macOS - - - iOS - - - - Android - + + {!setupKey && ( + <> + + + iOS + + + + Android + + + )} + - - - - - - + + + + + + {!setupKey && ( + <> + + + + )} + + {footer && ( @@ -158,3 +190,18 @@ export function SetupModalContent({ ); } + +type SetupKeyParameterProps = { + setupKey?: string; +}; + +export const SetupKeyParameter = ({ setupKey }: SetupKeyParameterProps) => { + return ( + setupKey && ( + <> + {" "} + --setup-key {setupKey}{" "} + + ) + ); +}; diff --git a/src/modules/setup-netbird-modal/WindowsTab.tsx b/src/modules/setup-netbird-modal/WindowsTab.tsx index 49bc7d0a..1a6a01ba 100644 --- a/src/modules/setup-netbird-modal/WindowsTab.tsx +++ b/src/modules/setup-netbird-modal/WindowsTab.tsx @@ -2,13 +2,18 @@ import Button from "@components/Button"; import Code from "@components/Code"; import Steps from "@components/Steps"; import TabsContentPadding, { TabsContent } from "@components/Tabs"; -import { GRPC_API_ORIGIN } from "@utils/netbird"; +import { getNetBirdUpCommand, GRPC_API_ORIGIN } from "@utils/netbird"; import { DownloadIcon, PackageOpenIcon } from "lucide-react"; import Link from "next/link"; import React from "react"; import { OperatingSystem } from "@/interfaces/OperatingSystem"; +import { SetupKeyParameter } from "@/modules/setup-netbird-modal/SetupModal"; -export default function WindowsTab() { +type Props = { + setupKey?: string; +}; + +export default function WindowsTab({ setupKey }: Readonly) { return ( @@ -44,15 +49,29 @@ export default function WindowsTab() { )} - -

    - {/* eslint-disable-next-line react/no-unescaped-entities */} - Click on "Connect" from the NetBird icon in your system tray -

    -
    - -

    Sign up using your email address

    -
    + {setupKey ? ( + +

    Open Command-line and run NetBird

    + + + {getNetBirdUpCommand()} + + + +
    + ) : ( + <> + +

    + {/* eslint-disable-next-line react/no-unescaped-entities */} + Click on "Connect" from the NetBird icon in your system tray +

    +
    + +

    Sign up using your email address

    +
    + + )}
    From f3d4dcb56778663a2af88525620588ece3b56f71 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Wed, 15 Jan 2025 16:17:27 +0100 Subject: [PATCH 17/26] Fix text for install netbird modal --- src/modules/setup-keys/SetupKeyModal.tsx | 19 ++++++++++++++----- .../setup-netbird-modal/SetupModal.tsx | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/modules/setup-keys/SetupKeyModal.tsx b/src/modules/setup-keys/SetupKeyModal.tsx index 28f9cdbf..57d45a80 100644 --- a/src/modules/setup-keys/SetupKeyModal.tsx +++ b/src/modules/setup-keys/SetupKeyModal.tsx @@ -41,12 +41,14 @@ type Props = { children?: React.ReactNode; open: boolean; setOpen: (open: boolean) => void; + name?: string; }; const copyMessage = "Setup-Key was copied to your clipboard!"; export default function SetupKeyModal({ children, open, setOpen, + name, }: Readonly) { const [successModal, setSuccessModal] = useState(false); const [setupKey, setSetupKey] = useState(); @@ -62,13 +64,16 @@ export default function SetupKeyModal({ <> {children && {children}} - + { + setInstallModal(state); + setOpen(false); + }} + key={installModal ? 2 : 3} > @@ -144,13 +149,17 @@ export default function SetupKeyModal({ type ModalProps = { onSuccess?: (setupKey: SetupKey) => void; + predefinedName?: string; }; -export function SetupKeyModalContent({ onSuccess }: Readonly) { +export function SetupKeyModalContent({ + onSuccess, + predefinedName = "", +}: Readonly) { const setupKeyRequest = useApiCall("/setup-keys", true); const { mutate } = useSWRConfig(); - const [name, setName] = useState(""); + const [name, setName] = useState(predefinedName); const [reusable, setReusable] = useState(false); const [usageLimit, setUsageLimit] = useState(""); const [expiresIn, setExpiresIn] = useState("7"); diff --git a/src/modules/setup-netbird-modal/SetupModal.tsx b/src/modules/setup-netbird-modal/SetupModal.tsx index 491ac971..7f88a94b 100644 --- a/src/modules/setup-netbird-modal/SetupModal.tsx +++ b/src/modules/setup-netbird-modal/SetupModal.tsx @@ -83,7 +83,7 @@ export function SetupModalContent({ {`It's time to add your first device.`} ) : ( - <>Install NetBird with Setup Key + <>Install NetBird{setupKey && " with Setup Key"} )}

    Date: Wed, 15 Jan 2025 16:42:42 +0100 Subject: [PATCH 18/26] Show resources count in group settings --- src/modules/settings/GroupsTable.tsx | 30 ++++++++++++++++++++++--- src/modules/settings/useGroupsUsage.tsx | 2 ++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/modules/settings/GroupsTable.tsx b/src/modules/settings/GroupsTable.tsx index d58ab8c2..e95c0742 100644 --- a/src/modules/settings/GroupsTable.tsx +++ b/src/modules/settings/GroupsTable.tsx @@ -4,7 +4,7 @@ import DataTableHeader from "@components/table/DataTableHeader"; import { DataTableRowsPerPage } from "@components/table/DataTableRowsPerPage"; import NoResults from "@components/ui/NoResults"; import { ColumnDef, SortingState } from "@tanstack/react-table"; -import { FolderGit2Icon } from "lucide-react"; +import { FolderGit2Icon, Layers3Icon } from "lucide-react"; import { usePathname } from "next/navigation"; import React from "react"; import AccessControlIcon from "@/assets/icons/AccessControlIcon"; @@ -138,6 +138,29 @@ export const GroupsTableColumns: ColumnDef[] = [ /> ), }, + { + accessorKey: "resources_count", + header: ({ column }) => { + return ( + Network Resources
    + } + > + + + ); + }, + cell: ({ row }) => ( + } + groupName={row.original.name} + text={"Network Resource(s)"} + count={row.original.resources_count} + /> + ), + }, { accessorKey: "users_count", header: ({ column }) => { @@ -172,7 +195,8 @@ export const GroupsTableColumns: ColumnDef[] = [ row.policies_count > 0 || row.routes_count > 0 || row.setup_keys_count > 0 || - row.users_count > 0 + row.users_count > 0 || + row.resources_count > 0 ); }, }, @@ -189,7 +213,7 @@ type Props = { headingTarget?: HTMLHeadingElement | null; }; -export default function GroupsTable({ headingTarget }: Props) { +export default function GroupsTable({ headingTarget }: Readonly) { const groups = useGroupsUsage(); const path = usePathname(); diff --git a/src/modules/settings/useGroupsUsage.tsx b/src/modules/settings/useGroupsUsage.tsx index 6e085d56..0551addf 100644 --- a/src/modules/settings/useGroupsUsage.tsx +++ b/src/modules/settings/useGroupsUsage.tsx @@ -16,6 +16,7 @@ export interface GroupUsage { routes_count: number; setup_keys_count: number; users_count: number; + resources_count: number; } export default function useGroupsUsage() { @@ -126,6 +127,7 @@ export default function useGroupsUsage() { id: group.id, name: group.name, peers_count: group.peers_count, + resources_count: group.resources_count, policies_count: policyCount, nameservers_count: nameserverCount, routes_count: routeCount, From 1a4102620766ace13e90928ad17c9d50200a93c2 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Wed, 15 Jan 2025 16:46:56 +0100 Subject: [PATCH 19/26] Fix "no results" and "no routing peers" text showing at the same time --- src/components/PeerSelector.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/PeerSelector.tsx b/src/components/PeerSelector.tsx index 365f63a4..7a1f050b 100644 --- a/src/components/PeerSelector.tsx +++ b/src/components/PeerSelector.tsx @@ -166,15 +166,17 @@ export function PeerSelector({ placeholder={"Search for peers by name or ip..."} /> - {unfilteredItems.length == 0 && ( - - { - "Seems like you don't have any linux peers to assign as a routing peer." - } - + {unfilteredItems.length == 0 && !search && ( +
    + + { + "Seems like you don't have any linux peers to assign as a routing peer." + } + +
    )} - {filteredItems.length == 0 && ( + {filteredItems.length == 0 && search != "" && ( There are no peers matching your search. From 33d8295745bde8d0c5261aaf1277f03e2d913e4c Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Wed, 15 Jan 2025 16:49:51 +0100 Subject: [PATCH 20/26] Fix wording --- .../networks/routing-peers/NetworkRoutingPeerModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx index 612a1c8b..2ccccb67 100644 --- a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx +++ b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx @@ -196,7 +196,7 @@ function RoutingPeerModalContent({ "text-nb-gray-500 group-data-[state=active]/trigger:text-netbird transition-all" } /> - Routers + Routing Peers @@ -226,7 +226,7 @@ function RoutingPeerModalContent({
    - Assign a single or multiple peers as a routing peers for the + Assign a single or multiple peers as routing peers for the network. From fe41574fe292f6390b7b8c4eae2256ceaf651bf9 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 16 Jan 2025 12:05:07 +0100 Subject: [PATCH 21/26] Fix resource policy count --- src/modules/networks/resources/ResourcePolicyCell.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/modules/networks/resources/ResourcePolicyCell.tsx b/src/modules/networks/resources/ResourcePolicyCell.tsx index 9551b493..e48c8cfa 100644 --- a/src/modules/networks/resources/ResourcePolicyCell.tsx +++ b/src/modules/networks/resources/ResourcePolicyCell.tsx @@ -22,13 +22,10 @@ export const ResourcePolicyCell = ({ resource }: Props) => { const resourceGroups = resource?.groups as Group[]; return policies?.filter((policy) => { if (!policy.enabled) return false; - const sourcePolicyGroups = policy.rules - ?.map((rule) => rule?.sources) - .flat() as Group[]; const destinationPolicyGroups = policy.rules ?.map((rule) => rule?.destinations) .flat() as Group[]; - const policyGroups = [...sourcePolicyGroups, ...destinationPolicyGroups]; + const policyGroups = [...destinationPolicyGroups]; return resourceGroups.some((resourceGroup) => policyGroups.some((policyGroup) => policyGroup.id === resourceGroup.id), ); From d3a8b7014a2d5867624aaa1a26d9e325366b0598 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 16 Jan 2025 14:03:37 +0100 Subject: [PATCH 22/26] Hide resource count when selection source groups --- src/components/PeerGroupSelector.tsx | 30 +++++++++++++++---- .../access-control/AccessControlModal.tsx | 5 ++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/components/PeerGroupSelector.tsx b/src/components/PeerGroupSelector.tsx index 904ed397..dfaf6a9b 100644 --- a/src/components/PeerGroupSelector.tsx +++ b/src/components/PeerGroupSelector.tsx @@ -30,7 +30,7 @@ import * as React from "react"; import { Fragment, useEffect, useMemo, useState } from "react"; import { useGroups } from "@/contexts/GroupsProvider"; import { useElementSize } from "@/hooks/useElementSize"; -import type {Group, GroupPeer, GroupResource} from "@/interfaces/Group"; +import type { Group, GroupPeer, GroupResource } from "@/interfaces/Group"; import { NetworkResource } from "@/interfaces/Network"; import type { Peer } from "@/interfaces/Peer"; @@ -48,6 +48,7 @@ interface MultiSelectProps { showRoutes?: boolean; disabledGroups?: Group[]; dataCy?: string; + showResourceCounter?: boolean; } export function PeerGroupSelector({ onChange, @@ -63,6 +64,7 @@ export function PeerGroupSelector({ showRoutes = false, disabledGroups, dataCy = "group-selector-dropdown", + showResourceCounter = true, }: Readonly) { const { groups, dropdownOptions, setDropdownOptions, addDropdownOptions } = useGroups(); @@ -105,20 +107,34 @@ export function PeerGroupSelector({ const groupPeers: GroupPeer[] | undefined = (group?.peers as GroupPeer[]) || []; const groupResources: GroupResource[] | undefined = - (group?.resources as GroupResource[]) || []; + (group?.resources as GroupResource[]) || []; if (peer) groupPeers?.push({ id: peer?.id as string, name: peer?.name }); if (!group && !option) { - addDropdownOptions([{ name: name, peers: groupPeers, resources: groupResources }]); + addDropdownOptions([ + { name: name, peers: groupPeers, resources: groupResources }, + ]); } if (max == 1 && values.length == 1) { - onChange([{ name: name, id: group?.id, peers: groupPeers, resources: groupResources }]); + onChange([ + { + name: name, + id: group?.id, + peers: groupPeers, + resources: groupResources, + }, + ]); } else { onChange((previous) => [ ...previous, - { name: name, id: group?.id, peers: groupPeers, resources: groupResources }, + { + name: name, + id: group?.id, + peers: groupPeers, + resources: groupResources, + }, ]); } @@ -396,7 +412,9 @@ export function PeerGroupSelector({ )} - + {showResourceCounter && ( + + )}
    Learn more about Access Controls From 31db28043bb444cd9c53a8f99320f1a1d11a4652 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 16 Jan 2025 17:05:28 +0100 Subject: [PATCH 23/26] Extend networks routing peer modal with option to create a setup key and install netbird --- src/components/PeerSelector.tsx | 2 +- .../routing-peers/NetworkRoutingPeerModal.tsx | 173 ++++++++++++------ src/modules/setup-keys/SetupKeyModal.tsx | 11 +- .../setup-netbird-modal/SetupModal.tsx | 49 +++-- 4 files changed, 154 insertions(+), 81 deletions(-) diff --git a/src/components/PeerSelector.tsx b/src/components/PeerSelector.tsx index 7a1f050b..8310ee60 100644 --- a/src/components/PeerSelector.tsx +++ b/src/components/PeerSelector.tsx @@ -170,7 +170,7 @@ export function PeerSelector({
    { - "Seems like you don't have any linux peers to assign as a routing peer." + "Seems like you don't have any Linux peers to assign as a routing peer." }
    diff --git a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx index 2ccccb67..6190856f 100644 --- a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx +++ b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx @@ -24,6 +24,7 @@ import { cn } from "@utils/helpers"; import { uniqBy } from "lodash"; import { ArrowDownWideNarrow, + CirclePlusIcon, ExternalLinkIcon, FolderGit2, MonitorSmartphoneIcon, @@ -37,6 +38,7 @@ import React, { useState } from "react"; import { Network, NetworkRouter } from "@/interfaces/Network"; import { Peer } from "@/interfaces/Peer"; import useGroupHelper from "@/modules/groups/useGroupHelper"; +import SetupKeyModal from "@/modules/setup-keys/SetupKeyModal"; type Props = { network: Network; @@ -178,6 +180,10 @@ function RoutingPeerModalContent({ }); }; + const [setupKeyModal, setSetupKeyModal] = useState(false); + + const canContinue = routingPeer !== undefined || routingPeerGroups.length > 0; + return ( - -
    - - - - - Routing Peers - + +
    +
    + { + setType(state); + setRoutingPeer(undefined); + setRoutingPeerGroups([]); + }} + > + + + + Routing Peers + - - - Peer Group - - - -
    - - Assign a single or multiple peers as routing peers for the - network. - - -
    -
    - -
    - - Assign a peer group with Linux machines to be used as - routing peers. - - -
    -
    -
    + + + Peer Group + + + +
    + + Assign a single or multiple peers as routing peers for the + network. + + +
    +
    + +
    + + Assign a peer group with Linux machines to be used as + routing peers. + + +
    +
    + +
    + +
    +
    + + + You can install NetBird with a Setup Key on one or more Linux + machines to act as routing peers. + +
    + + {setupKeyModal && ( + + )} +
    @@ -321,31 +366,43 @@ function RoutingPeerModalContent({
    - - - {tab == "router" && ( - + <> + + + + + )} {tab == "settings" && ( - + <> + + + + )}
    diff --git a/src/modules/setup-keys/SetupKeyModal.tsx b/src/modules/setup-keys/SetupKeyModal.tsx index 57d45a80..97291d4a 100644 --- a/src/modules/setup-keys/SetupKeyModal.tsx +++ b/src/modules/setup-keys/SetupKeyModal.tsx @@ -32,7 +32,6 @@ import { import React, { useMemo, useState } from "react"; import { useSWRConfig } from "swr"; import SetupKeysIcon from "@/assets/icons/SetupKeysIcon"; -import useCopyToClipboard from "@/hooks/useCopyToClipboard"; import { SetupKey } from "@/interfaces/SetupKey"; import useGroupHelper from "@/modules/groups/useGroupHelper"; import SetupModal from "@/modules/setup-netbird-modal/SetupModal"; @@ -42,6 +41,7 @@ type Props = { open: boolean; setOpen: (open: boolean) => void; name?: string; + showOnlyRoutingPeerOS?: boolean; }; const copyMessage = "Setup-Key was copied to your clipboard!"; export default function SetupKeyModal({ @@ -49,12 +49,11 @@ export default function SetupKeyModal({ open, setOpen, name, + showOnlyRoutingPeerOS, }: Readonly) { const [successModal, setSuccessModal] = useState(false); const [setupKey, setSetupKey] = useState(); const [installModal, setInstallModal] = useState(false); - const [, copy] = useCopyToClipboard(setupKey?.key); - const handleSuccess = (setupKey: SetupKey) => { setSetupKey(setupKey); setSuccessModal(true); @@ -75,7 +74,11 @@ export default function SetupKeyModal({ }} key={installModal ? 2 : 3} > - + ) { return ( - + ); } @@ -53,6 +59,7 @@ type SetupModalContentProps = { footer?: boolean; tabAlignment?: "center" | "start" | "end"; setupKey?: string; + showOnlyRoutingPeerOS?: boolean; }; export function SetupModalContent({ @@ -61,6 +68,7 @@ export function SetupModalContent({ footer = true, tabAlignment = "center", setupKey, + showOnlyRoutingPeerOS, }: Readonly) { const os = useOperatingSystem(); const [isFirstRun] = useLocalStorage("netbird-first-run", true); @@ -90,7 +98,7 @@ export function SetupModalContent({ className={cn("mx-auto mt-3", setupKey ? "max-w-sm" : "max-w-xs")} > {setupKey - ? "To get started, install and run NetBird with your recently created setup key as a parameter." + ? "To get started, install and run NetBird with the setup key as a parameter." : "To get started, install NetBird and log in with your email account."}
    @@ -106,22 +114,27 @@ export function SetupModalContent({ /> Linux
    - - - Windows - - - - macOS - + + {!showOnlyRoutingPeerOS && ( + <> + + + Windows + + + + macOS + + + )} {!setupKey && ( <> From 5c2dba72673e3befec310df6772b2ea9adcbef49 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Fri, 17 Jan 2025 11:54:52 +0100 Subject: [PATCH 24/26] Add option for horizontal stepper --- src/components/Steps.tsx | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/components/Steps.tsx b/src/components/Steps.tsx index 6ec2c482..32d2fab2 100644 --- a/src/components/Steps.tsx +++ b/src/components/Steps.tsx @@ -4,9 +4,18 @@ import React from "react"; type Props = { children: React.ReactNode; className?: string; + horizontal?: boolean; }; -export default function Steps({ children, className }: Props) { - return
    {children}
    ; +export default function Steps({ + children, + className, + horizontal = false, +}: Readonly) { + return ( +
    + {children} +
    + ); } type StepProps = { @@ -14,21 +23,32 @@ type StepProps = { step: number; line?: boolean; center?: boolean; + horizontal?: boolean; }; -const Step = ({ children, step, line = true, center = false }: StepProps) => { +const Step = ({ + children, + step, + line = true, + center = false, + horizontal, +}: StepProps) => { return (
    {line && ( )} From 48bf8e0d99e24a11f167302fde255d4be953ac84 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Fri, 17 Jan 2025 14:20:13 +0100 Subject: [PATCH 25/26] Generate setup key when installing netbird from routing peer modal --- .../routing-peers/NetworkRoutingPeerModal.tsx | 106 ++++++++++++++---- src/modules/setup-netbird-modal/DockerTab.tsx | 12 +- src/modules/setup-netbird-modal/LinuxTab.tsx | 21 +++- .../setup-netbird-modal/SetupModal.tsx | 26 ++++- 4 files changed, 133 insertions(+), 32 deletions(-) diff --git a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx index 6190856f..95def8dd 100644 --- a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx +++ b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx @@ -24,9 +24,10 @@ import { cn } from "@utils/helpers"; import { uniqBy } from "lodash"; import { ArrowDownWideNarrow, - CirclePlusIcon, + DownloadIcon, ExternalLinkIcon, FolderGit2, + Loader2, MonitorSmartphoneIcon, PlusCircle, Power, @@ -35,10 +36,12 @@ import { VenetianMask, } from "lucide-react"; import React, { useState } from "react"; +import { useSWRConfig } from "swr"; import { Network, NetworkRouter } from "@/interfaces/Network"; import { Peer } from "@/interfaces/Peer"; +import { SetupKey } from "@/interfaces/SetupKey"; import useGroupHelper from "@/modules/groups/useGroupHelper"; -import SetupKeyModal from "@/modules/setup-keys/SetupKeyModal"; +import SetupModal from "@/modules/setup-netbird-modal/SetupModal"; type Props = { network: Network; @@ -240,8 +243,8 @@ function RoutingPeerModalContent({
    - Assign a single or multiple peers as routing peers for the - network. + Assign a single or multiple Linux peers as routing peers + for the network.
    - + - You can install NetBird with a Setup Key on one or more Linux + You can install NetBird with a setup key on one or more Linux machines to act as routing peers.
    - - {setupKeyModal && ( - - )} +
    @@ -409,3 +398,74 @@ function RoutingPeerModalContent({ ); } + +type InstallNetBirdWithSetupKeyButtonProps = { + name?: string; +}; + +const InstallNetBirdWithSetupKeyButton = ({ + name, +}: InstallNetBirdWithSetupKeyButtonProps) => { + const setupKeyRequest = useApiCall("/setup-keys", true); + const { mutate } = useSWRConfig(); + + const [installModal, setInstallModal] = useState(false); + const [setupKey, setSetupKey] = useState(); + const [isLoading, setIsLoading] = useState(false); + + const createSetupKey = async () => { + const loadingTimeout = setTimeout(() => setIsLoading(true), 1000); + + await setupKeyRequest + .post({ + name, + type: "one-off", + expires_in: 24 * 60 * 60, // 1 day expiration + revoked: false, + auto_groups: [], + usage_limit: 1, + ephemeral: false, + }) + .then((setupKey) => { + setInstallModal(true); + setSetupKey(setupKey); + mutate("/setup-keys"); + }) + .finally(() => { + setIsLoading(false); + clearTimeout(loadingTimeout); + }); + }; + + return ( + <> + + {setupKey && ( + + + + )} + + ); +}; diff --git a/src/modules/setup-netbird-modal/DockerTab.tsx b/src/modules/setup-netbird-modal/DockerTab.tsx index 546f1020..e6cd059d 100644 --- a/src/modules/setup-netbird-modal/DockerTab.tsx +++ b/src/modules/setup-netbird-modal/DockerTab.tsx @@ -9,12 +9,17 @@ import { ExternalLinkIcon } from "lucide-react"; import Link from "next/link"; import React from "react"; import { OperatingSystem } from "@/interfaces/OperatingSystem"; +import { RoutingPeerSetupKeyInfo } from "@/modules/setup-netbird-modal/SetupModal"; type Props = { setupKey?: string; + showSetupKeyInfo?: boolean; }; -export default function DockerTab({ setupKey }: Readonly) { +export default function DockerTab({ + setupKey, + showSetupKeyInfo = false, +}: Readonly) { return ( @@ -39,7 +44,10 @@ export default function DockerTab({ setupKey }: Readonly) {
    -

    Run NetBird container

    +

    + Run NetBird container + {showSetupKeyInfo && } +

    docker run --rm -d \ --cap-add=NET_ADMIN \ diff --git a/src/modules/setup-netbird-modal/LinuxTab.tsx b/src/modules/setup-netbird-modal/LinuxTab.tsx index 51fc5034..581e82d5 100644 --- a/src/modules/setup-netbird-modal/LinuxTab.tsx +++ b/src/modules/setup-netbird-modal/LinuxTab.tsx @@ -13,13 +13,20 @@ import { getNetBirdUpCommand } from "@utils/netbird"; import { TerminalSquareIcon } from "lucide-react"; import React from "react"; import { OperatingSystem } from "@/interfaces/OperatingSystem"; -import { SetupKeyParameter } from "@/modules/setup-netbird-modal/SetupModal"; +import { + RoutingPeerSetupKeyInfo, + SetupKeyParameter, +} from "@/modules/setup-netbird-modal/SetupModal"; type Props = { setupKey?: string; + showSetupKeyInfo?: boolean; }; -export default function LinuxTab({ setupKey }: Readonly) { +export default function LinuxTab({ + setupKey, + showSetupKeyInfo = false, +}: Readonly) { return ( @@ -32,7 +39,10 @@ export default function LinuxTab({ setupKey }: Readonly) { curl -fsSL https://pkgs.netbird.io/install.sh | sh
    -

    Run NetBird {!setupKey && "and log in the browser"}

    +

    + Run NetBird {!setupKey && "and log in the browser"} + {showSetupKeyInfo && } +

    {getNetBirdUpCommand()} @@ -86,7 +96,10 @@ export default function LinuxTab({ setupKey }: Readonly) {
    -

    Run NetBird {!setupKey && "and log in the browser"}

    +

    + Run NetBird {!setupKey && "and log in the browser"} + {showSetupKeyInfo && } +

    {getNetBirdUpCommand()} diff --git a/src/modules/setup-netbird-modal/SetupModal.tsx b/src/modules/setup-netbird-modal/SetupModal.tsx index 7e04dacb..99923104 100644 --- a/src/modules/setup-netbird-modal/SetupModal.tsx +++ b/src/modules/setup-netbird-modal/SetupModal.tsx @@ -167,7 +167,10 @@ export function SetupModalContent({ - + @@ -178,7 +181,10 @@ export function SetupModalContent({ )} - + {footer && ( @@ -213,8 +219,22 @@ export const SetupKeyParameter = ({ setupKey }: SetupKeyParameterProps) => { setupKey && ( <> {" "} - --setup-key {setupKey}{" "} + --setup-key {setupKey} ) ); }; + +export const RoutingPeerSetupKeyInfo = () => { + return ( +
    + This setup key can be used only once within the next 24 hours. +
    + When expired, the same key can not be used again. +
    + ); +}; From 44f28c81245a16f782a86ac7b3110efac2bb8af5 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Mon, 20 Jan 2025 11:29:42 +0100 Subject: [PATCH 26/26] Add confirm dialog to let the user know a one-off setup-key will be created. This avoids accidental clicking and later confusion on the setup keys page --- .../routing-peers/NetworkRoutingPeerModal.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx index 95def8dd..16651981 100644 --- a/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx +++ b/src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx @@ -37,6 +37,7 @@ import { } from "lucide-react"; import React, { useState } from "react"; import { useSWRConfig } from "swr"; +import { useDialog } from "@/contexts/DialogProvider"; import { Network, NetworkRouter } from "@/interfaces/Network"; import { Peer } from "@/interfaces/Peer"; import { SetupKey } from "@/interfaces/SetupKey"; @@ -408,12 +409,23 @@ const InstallNetBirdWithSetupKeyButton = ({ }: InstallNetBirdWithSetupKeyButtonProps) => { const setupKeyRequest = useApiCall("/setup-keys", true); const { mutate } = useSWRConfig(); + const { confirm } = useDialog(); const [installModal, setInstallModal] = useState(false); const [setupKey, setSetupKey] = useState(); const [isLoading, setIsLoading] = useState(false); const createSetupKey = async () => { + const choice = await confirm({ + title: `Create a Setup Key?`, + description: + "If you continue, a one-off setup key will be automatically created and you will be able to install NetBird on a Linux machine.", + confirmText: "Continue", + cancelText: "Cancel", + type: "default", + }); + if (!choice) return; + const loadingTimeout = setTimeout(() => setIsLoading(true), 1000); await setupKeyRequest