From 21abbfa56192ce75f0855b01b2e8383afb125e1b Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 29 Feb 2024 02:36:18 +0100 Subject: [PATCH] Add necessary changes for IPv6 support --- src/app/(dashboard)/peer/page.tsx | 246 +++++++++++------- src/components/PeerGroupSelector.tsx | 6 +- src/contexts/PeerProvider.tsx | 7 +- src/interfaces/Group.ts | 1 + src/interfaces/Peer.ts | 3 + src/modules/groups/useGroupHelper.tsx | 3 + src/modules/peers/PeerActionCell.tsx | 2 + src/modules/peers/PeerAddressCell.tsx | 13 +- .../peers/PeerAddressTooltipContent.tsx | 9 +- src/modules/peers/PeerStatusCell.tsx | 1 + src/modules/peers/PeersTable.tsx | 5 + src/modules/settings/GroupsActionCell.tsx | 32 ++- src/modules/settings/useGroupsUsage.tsx | 2 + 13 files changed, 223 insertions(+), 107 deletions(-) diff --git a/src/app/(dashboard)/peer/page.tsx b/src/app/(dashboard)/peer/page.tsx index 3863e7c1..23603a6c 100644 --- a/src/app/(dashboard)/peer/page.tsx +++ b/src/app/(dashboard)/peer/page.tsx @@ -58,6 +58,7 @@ import PageContainer from "@/layouts/PageContainer"; import useGroupHelper from "@/modules/groups/useGroupHelper"; import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton"; import PeerRoutesTable from "@/modules/peer/PeerRoutesTable"; +import {SelectDropdown} from "@components/select/SelectDropdown"; export default function PeerPage() { const queryParameter = useSearchParams(); @@ -82,6 +83,9 @@ function PeerOverview() { const [loginExpiration, setLoginExpiration] = useState( peer.login_expiration_enabled, ); + const [ipv6Enabled, setIpv6Enabled] = useState( + peer.ipv6_enabled, + ); const [selectedGroups, setSelectedGroups, { getAllGroupCalls }] = useGroupHelper({ initial: peerGroups, @@ -104,10 +108,11 @@ function PeerOverview() { ssh, selectedGroups, loginExpiration, + ipv6Enabled ]); const updatePeer = async () => { - const updateRequest = update(name, ssh, loginExpiration); + const updateRequest = update(name, ssh, loginExpiration, ipv6Enabled); const groupCalls = getAllGroupCalls(); const batchCall = groupCalls ? [...groupCalls, updateRequest] @@ -118,7 +123,7 @@ function PeerOverview() { promise: Promise.all(batchCall).then(() => { mutate("/peers/" + peer.id); mutate("/groups"); - updateHasChangedRef([name, ssh, selectedGroups, loginExpiration]); + updateHasChangedRef([name, ssh, selectedGroups, loginExpiration, ipv6Enabled]); }), loadingMessage: "Saving the peer...", }); @@ -130,11 +135,11 @@ function PeerOverview() {
} + href={"/peers"} + label={"Peers"} + icon={} /> - +
@@ -142,37 +147,37 @@ function PeerOverview() {

- +
- +
{ - setName(newName); - setShowEditNameModal(false); - }} - peer={peer} - initialName={name} - key={showEditNameModal ? 1 : 0} + onSuccess={(newName) => { + setName(newName); + setShowEditNameModal(false); + }} + peer={peer} + initialName={name} + key={showEditNameModal ? 1 : 0} />

- +
@@ -182,17 +187,17 @@ function PeerOverview() {
@@ -200,57 +205,57 @@ function PeerOverview() {
- +
- - + content={ +
+ + Login expiration is disabled for all peers added with an setup-key. -
- } - className={"w-full block"} - disabled={!!peer.user_id} +
+ } + className={"w-full block"} + disabled={!!peer.user_id} > + + Login Expiration + + } + helpText={ + "Enable to require SSO login peers to re-authenticate when their login expires." + } + /> + + + !set + ? setSsh(false) + : openSSHDialog().then((confirm) => setSsh(confirm)) + } label={ <> - - Login Expiration + + SSH Access } helpText={ - "Enable to require SSO login peers to re-authenticate when their login expires." + "Enable the SSH server on this peer to access the machine via an secure shell." } - /> - - - !set - ? setSsh(false) - : openSSHDialog().then((confirm) => setSsh(confirm)) - } - label={ - <> - - SSH Access - - } - helpText={ - "Enable the SSH server on this peer to access the machine via an secure shell." - } />
@@ -258,62 +263,105 @@ function PeerOverview() { Use groups to control what this peer can access.
+
+ + + Whether to enable IPv6, disable it, or enable IPv6 if at least one group has it enabled. + + + + + IPv6 Support requires a recent version of the NetBird client as well as a supported OS (Linux with nftables). + +
+ } + className={"w-full block"} + disabled={peer.ipv6_supported} + > + + +
- + {isLinux ? ( -
-
-
-
-

Network Routes

- - Access other networks without installing NetBird on every - resource. - -
-
+
+
+
- +

Network Routes

+ + Access other networks without installing NetBird on every + resource. + +
+
+
+ +
+
-
-
) : null} ); } -function PeerInformationCard({ peer }: { peer: Peer }) { - const { isLoading, getRegionByPeer } = useCountries(); +function PeerInformationCard({peer}: { peer: Peer }) { + const {isLoading, getRegionByPeer} = useCountries(); const countryText = useMemo(() => { return getRegionByPeer(peer); }, [getRegionByPeer, peer]); return ( - - - - - NetBird IP-Address - - } - value={peer.ip} - /> + + + + + NetBird IPv4-Address + + } + value={peer.ip} + /> + + + + NetBird IPv6-Address + + } + value={peer.ip6} + /> [ ...previous, - { name: name, peers: groupPeers }, + { name: name, peers: groupPeers, ipv6_enabled: false }, ]); } if (max == 1 && values.length == 1) { - onChange([{ name: name, id: group?.id, peers: groupPeers }]); + onChange([{ name: name, id: group?.id, peers: groupPeers, ipv6_enabled: group == null ? false : group.ipv6_enabled }]); } else { onChange((previous) => [ ...previous, - { name: name, id: group?.id, peers: groupPeers }, + { name: name, id: group?.id, peers: groupPeers, ipv6_enabled: group == null ? false : group.ipv6_enabled }, ]); } diff --git a/src/contexts/PeerProvider.tsx b/src/contexts/PeerProvider.tsx index 7c153da1..95019f52 100644 --- a/src/contexts/PeerProvider.tsx +++ b/src/contexts/PeerProvider.tsx @@ -24,7 +24,8 @@ const PeerContext = React.createContext( name: string, ssh: boolean, loginExpiration: boolean, - approval_required?: boolean, + ipv6_enabled: string, + approval_required?: boolean ) => Promise; openSSHDialog: () => Promise; deletePeer: () => void; @@ -65,7 +66,8 @@ export default function PeerProvider({ children, peer }: Props) { name: string, ssh: boolean, loginExpiration: boolean, - approval_required?: boolean, + ipv6_enabled: string, + approval_required?: boolean ) => { return peerRequest.put( { @@ -80,6 +82,7 @@ export default function PeerProvider({ children, peer }: Props) { approval_required != undefined ? approval_required : peer.approval_required, + ipv6_enabled: ipv6_enabled != undefined ? ipv6_enabled : peer.ipv6_enabled }, `/${peer.id}`, ); diff --git a/src/interfaces/Group.ts b/src/interfaces/Group.ts index 8a6d91a3..b93f197c 100644 --- a/src/interfaces/Group.ts +++ b/src/interfaces/Group.ts @@ -3,6 +3,7 @@ export interface Group { name: string; peers?: GroupPeer[] | string[]; peers_count?: number; + ipv6_enabled: boolean } export interface GroupPeer { diff --git a/src/interfaces/Peer.ts b/src/interfaces/Peer.ts index 9e09bc86..4e6f711c 100644 --- a/src/interfaces/Peer.ts +++ b/src/interfaces/Peer.ts @@ -5,6 +5,7 @@ export interface Peer { id?: string; name: string; ip: string; + ip6?: string, connected: boolean; last_seen: Date; os: string; @@ -19,6 +20,8 @@ export interface Peer { last_login: Date; login_expired: boolean; login_expiration_enabled: boolean; + ipv6_supported: boolean, + ipv6_enabled: string, approval_required: boolean; city_name: string; country_code: string; diff --git a/src/modules/groups/useGroupHelper.tsx b/src/modules/groups/useGroupHelper.tsx index 1b6cdde6..7398ce10 100644 --- a/src/modules/groups/useGroupHelper.tsx +++ b/src/modules/groups/useGroupHelper.tsx @@ -80,6 +80,7 @@ export default function useGroupHelper({ initial = [], peer }: Props) { return groupRequest.put( { name: g.name, + ipv6_enabled: g.ipv6_enabled, peers: newPeerGroups ? newPeerGroups.map((p) => { const groupPeer = p as GroupPeer; @@ -111,6 +112,7 @@ export default function useGroupHelper({ initial = [], peer }: Props) { return groupRequest.put( { name: selectedGroup.name, + ipv6_enabled: selectedGroup.ipv6_enabled, peers: peers, }, `/${selectedGroup.id}`, @@ -121,6 +123,7 @@ export default function useGroupHelper({ initial = [], peer }: Props) { return groupRequest .post({ name: selectedGroup.name, + ipv6_enabled: selectedGroup.ipv6_enabled, peers: groupPeers || [], }) .then((group) => { diff --git a/src/modules/peers/PeerActionCell.tsx b/src/modules/peers/PeerActionCell.tsx index 6dd426ac..53501849 100644 --- a/src/modules/peers/PeerActionCell.tsx +++ b/src/modules/peers/PeerActionCell.tsx @@ -34,6 +34,7 @@ export default function PeerActionCell() { peer.name, peer.ssh_enabled, !peer.login_expiration_enabled, + peer.ipv6_enabled, ).then(() => { mutate("/peers"); mutate("/groups"); @@ -51,6 +52,7 @@ export default function PeerActionCell() { peer.name, !peer.ssh_enabled, peer.login_expiration_enabled, + peer.ipv6_enabled, ).then(() => { mutate("/peers"); mutate("/groups"); diff --git a/src/modules/peers/PeerAddressCell.tsx b/src/modules/peers/PeerAddressCell.tsx index 145046b6..e9cbf75b 100644 --- a/src/modules/peers/PeerAddressCell.tsx +++ b/src/modules/peers/PeerAddressCell.tsx @@ -46,7 +46,7 @@ export default function PeerAddressCell({ peer }: Props) { {peer.dns_label} + {peer.ip6 != null ? ( + + + {peer.ip6} + + + ) : null}
diff --git a/src/modules/peers/PeerAddressTooltipContent.tsx b/src/modules/peers/PeerAddressTooltipContent.tsx index 912f7676..9bfd697e 100644 --- a/src/modules/peers/PeerAddressTooltipContent.tsx +++ b/src/modules/peers/PeerAddressTooltipContent.tsx @@ -25,9 +25,16 @@ export const PeerAddressTooltipContent = ({ peer }: Props) => { > } - label={"NetBird IP"} + label={"NetBird IPv4"} value={peer.ip} /> + {peer.ip6 != null ? ( + } + label={"NetBird IPv6"} + value={peer.ip6} + /> + ) : null} } label={"Public IP"} diff --git a/src/modules/peers/PeerStatusCell.tsx b/src/modules/peers/PeerStatusCell.tsx index cd5efd5b..fc009a71 100644 --- a/src/modules/peers/PeerStatusCell.tsx +++ b/src/modules/peers/PeerStatusCell.tsx @@ -40,6 +40,7 @@ export default function PeerStatusCell({ peer }: Props) { peer.name, peer.ssh_enabled, peer.login_expiration_enabled, + peer.ipv6_enabled, false, ).then(() => { mutate("/peers"); diff --git a/src/modules/peers/PeersTable.tsx b/src/modules/peers/PeersTable.tsx index 832c9515..69ef92a9 100644 --- a/src/modules/peers/PeersTable.tsx +++ b/src/modules/peers/PeersTable.tsx @@ -54,6 +54,10 @@ const PeersTableColumns: ColumnDef[] = [ accessorKey: "ip", sortingFn: "text", }, + { + accessorKey: "ip6", + sortingFn: "text", + }, { id: "user_name", accessorFn: (peer) => (peer.user ? peer.user?.name : "Unknown"), @@ -191,6 +195,7 @@ export default function PeersTable({ peers, isLoading }: Props) { group_name_strings: false, group_names: false, ip: false, + ip6: false, user_name: false, user_email: false, }} diff --git a/src/modules/settings/GroupsActionCell.tsx b/src/modules/settings/GroupsActionCell.tsx index 1b26cf5f..cdd78975 100644 --- a/src/modules/settings/GroupsActionCell.tsx +++ b/src/modules/settings/GroupsActionCell.tsx @@ -3,11 +3,13 @@ import FullTooltip from "@components/FullTooltip"; import { notify } from "@components/Notification"; import { useApiCall } from "@utils/api"; import { Trash2 } from "lucide-react"; -import React from "react"; +import React, {useMemo} from "react"; import { useSWRConfig } from "swr"; import { useDialog } from "@/contexts/DialogProvider"; import { SetupKey } from "@/interfaces/SetupKey"; import { GroupUsage } from "@/modules/settings/useGroupsUsage"; +import {ToggleSwitch} from "@components/ToggleSwitch"; +import type {Group, GroupPeer} from "@/interfaces/Group"; type Props = { group: GroupUsage; @@ -16,6 +18,7 @@ type Props = { export default function GroupsActionCell({ group, in_use }: Props) { const { confirm } = useDialog(); const deleteRequest = useApiCall("/groups/" + group.id); + const updateRequest = useApiCall("/groups/" + group.id); const { mutate } = useSWRConfig(); const handleRevoke = async () => { @@ -42,8 +45,35 @@ export default function GroupsActionCell({ group, in_use }: Props) { handleRevoke().then(); }; + const ipv6IsEnabled = useMemo(() => { + return group.original_group.ipv6_enabled; + }, [group]); + + const handleIpv6Change = async (newValue: boolean) => { + return updateRequest.put( + { + name: group.name, + peers: group.original_group.peers?.map((p) => { + if (typeof p == "string") { + return p + } else { + return p.id + } + }), + ipv6_enabled: newValue + }, + ).then((g) => { + mutate("/groups") + }); + }; + return (
+ handleIpv6Change(!ipv6IsEnabled)} + >