Skip to content

Commit 3ba7acd

Browse files
authored
Add new networks feature (#427)
1 parent c7775ad commit 3ba7acd

File tree

85 files changed

+3872
-315
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+3872
-315
lines changed

package-lock.json

Lines changed: 373 additions & 196 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
"framer-motion": "^10.16.4",
5656
"ip-cidr": "^3.1.0",
5757
"lodash": "^4.17.21",
58-
"lucide-react": "^0.383.0",
59-
"next": "13.5.5",
58+
"lucide-react": "^0.460.0",
59+
"next": "13.5.7",
6060
"next-themes": "^0.2.1",
6161
"punycode": "^2.3.1",
6262
"react": "^18",
@@ -76,7 +76,7 @@
7676
"typescript": "^5"
7777
},
7878
"devDependencies": {
79-
"cypress": "^13.3.3",
79+
"cypress": "^13.13.0",
8080
"postcss": "^8",
8181
"prettier": "3.0.3",
8282
"tailwindcss": "^3"

src/app/(dashboard)/dns/settings/page.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,24 @@ import { IconSettings2 } from "@tabler/icons-react";
1414
import useFetchApi, { useApiCall } from "@utils/api";
1515
import { ExternalLinkIcon } from "lucide-react";
1616
import React from "react";
17+
import Skeleton from "react-loading-skeleton";
1718
import { useSWRConfig } from "swr";
1819
import DNSIcon from "@/assets/icons/DNSIcon";
1920
import { useHasChanges } from "@/hooks/useHasChanges";
21+
import { Group } from "@/interfaces/Group";
2022
import { NameserverSettings } from "@/interfaces/NameserverSettings";
2123
import PageContainer from "@/layouts/PageContainer";
2224
import useGroupHelper from "@/modules/groups/useGroupHelper";
25+
import { useGroupIdsToGroups } from "@/modules/groups/useGroupIdsToGroups";
2326

2427
export default function NameServerSettings() {
2528
const { data: settings, isLoading } =
2629
useFetchApi<NameserverSettings>("/dns/settings");
2730

31+
const initialDNSGroups = useGroupIdsToGroups(
32+
settings?.disabled_management_groups,
33+
);
34+
2835
return (
2936
<PageContainer>
3037
<div className={"p-default py-6"}>
@@ -55,10 +62,16 @@ export default function NameServerSettings() {
5562
in our documentation.
5663
</Paragraph>
5764
<RestrictedAccess page={"DNS Settings"}>
58-
{!isLoading && (
59-
<SettingDisabledManagementGroups
60-
initial={settings?.disabled_management_groups}
61-
/>
65+
{!isLoading && initialDNSGroups !== undefined ? (
66+
<SettingDisabledManagementGroups initialGroups={initialDNSGroups} />
67+
) : (
68+
<div>
69+
<Skeleton
70+
width={"100%"}
71+
className={"mt-8 max-w-xl"}
72+
height={240}
73+
/>
74+
</div>
6275
)}
6376
</RestrictedAccess>
6477
</div>
@@ -67,16 +80,16 @@ export default function NameServerSettings() {
6780
}
6881

6982
const SettingDisabledManagementGroups = ({
70-
initial,
83+
initialGroups,
7184
}: {
72-
initial: string[] | undefined;
85+
initialGroups: Group[];
7386
}) => {
7487
const settingRequest = useApiCall<NameserverSettings>("/dns/settings");
7588
const { mutate } = useSWRConfig();
7689

7790
const [selectedGroups, setSelectedGroups, { save: saveGroups }] =
7891
useGroupHelper({
79-
initial: initial || [],
92+
initial: initialGroups,
8093
});
8194

8295
const { hasChanges, updateRef: updateChangesRef } = useHasChanges([
@@ -108,6 +121,7 @@ const SettingDisabledManagementGroups = ({
108121
Peers in these groups will require manual domain name resolution
109122
</HelpText>
110123
<PeerGroupSelector
124+
dataCy={"dns-groups-selector"}
111125
onChange={setSelectedGroups}
112126
values={selectedGroups}
113127
/>
@@ -122,6 +136,7 @@ const SettingDisabledManagementGroups = ({
122136
size={"sm"}
123137
onClick={saveSettings}
124138
disabled={!hasChanges}
139+
data-cy={"save-changes"}
125140
>
126141
Save Changes
127142
</Button>

src/app/(dashboard)/network-routes/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import PeersProvider from "@/contexts/PeersProvider";
1414
import RoutesProvider from "@/contexts/RoutesProvider";
1515
import { Route } from "@/interfaces/Route";
1616
import PageContainer from "@/layouts/PageContainer";
17+
import { NetworkRoutesDeprecationInfo } from "@/modules/networks/misc/NetworkRoutesDeprecationInfo";
1718
import useGroupedRoutes from "@/modules/route-group/useGroupedRoutes";
1819

1920
const NetworkRoutesTable = lazy(
@@ -39,7 +40,9 @@ export default function NetworkRoutes() {
3940
icon={<NetworkRoutesIcon size={13} />}
4041
/>
4142
</Breadcrumbs>
42-
<h1 ref={headingRef}>Network Routes</h1>
43+
<h1 ref={headingRef}>
44+
Network Routes <NetworkRoutesDeprecationInfo size={18} />
45+
</h1>
4346
<Paragraph>
4447
Network routes allow you to access other networks like LANs and
4548
VPCs without installing NetBird on every resource.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { globalMetaTitle } from "@utils/meta";
2+
import type { Metadata } from "next";
3+
import BlankLayout from "@/layouts/BlankLayout";
4+
5+
export const metadata: Metadata = {
6+
title: `Network - Networks - ${globalMetaTitle}`,
7+
};
8+
export default BlankLayout;
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
"use client";
2+
3+
import Breadcrumbs from "@components/Breadcrumbs";
4+
import Card from "@components/Card";
5+
import FullTooltip from "@components/FullTooltip";
6+
import InlineLink from "@components/InlineLink";
7+
import Separator from "@components/Separator";
8+
import FullScreenLoading from "@components/ui/FullScreenLoading";
9+
import useRedirect from "@hooks/useRedirect";
10+
import useFetchApi from "@utils/api";
11+
import { cn } from "@utils/helpers";
12+
import {
13+
ArrowUpRightIcon,
14+
HelpCircle,
15+
PencilLineIcon,
16+
ServerIcon,
17+
ShieldCheckIcon,
18+
ShieldXIcon,
19+
} from "lucide-react";
20+
import { useSearchParams } from "next/navigation";
21+
import React, { useMemo, useState } from "react";
22+
import { useSWRConfig } from "swr";
23+
import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon";
24+
import { useLoggedInUser } from "@/contexts/UsersProvider";
25+
import { Network } from "@/interfaces/Network";
26+
import PageContainer from "@/layouts/PageContainer";
27+
import { NetworkInformationSquare } from "@/modules/networks/misc/NetworkInformationSquare";
28+
import NetworkModal from "@/modules/networks/NetworkModal";
29+
import { NetworkProvider } from "@/modules/networks/NetworkProvider";
30+
import { ResourcesSection } from "@/modules/networks/resources/ResourcesSection";
31+
import { NetworkRoutingPeersSection } from "@/modules/networks/routing-peers/NetworkRoutingPeersSection";
32+
33+
export default function NetworkDetailPage() {
34+
const queryParameter = useSearchParams();
35+
const networkId = queryParameter.get("id");
36+
const { data: network, isLoading } = useFetchApi<Network>(
37+
`/networks/${networkId}`,
38+
true,
39+
);
40+
41+
useRedirect("/networks", false, !networkId);
42+
43+
return network && !isLoading ? (
44+
<NetworkOverview network={network} />
45+
) : (
46+
<FullScreenLoading />
47+
);
48+
}
49+
50+
function NetworkOverview({ network }: Readonly<{ network: Network }>) {
51+
const { isUser } = useLoggedInUser();
52+
const [networkModal, setNetworkModal] = useState(false);
53+
const { mutate } = useSWRConfig();
54+
55+
const isActive = !!(
56+
network?.routing_peers_count && network.routing_peers_count > 0
57+
);
58+
59+
return (
60+
<PageContainer>
61+
<NetworkProvider network={network}>
62+
<div className={"p-default py-6 mb-4"}>
63+
<Breadcrumbs>
64+
<Breadcrumbs.Item
65+
href={"/networks"}
66+
label={"Networks"}
67+
disabled={isUser}
68+
icon={<NetworkRoutesIcon size={13} />}
69+
/>
70+
<Breadcrumbs.Item
71+
href={"/network"}
72+
label={network.name}
73+
active={true}
74+
/>
75+
</Breadcrumbs>
76+
77+
<div className={"flex justify-between max-w-6xl"}>
78+
<div
79+
className={cn(
80+
"flex items-center",
81+
!network.description && "gap-2",
82+
)}
83+
>
84+
<NetworkInformationSquare
85+
name={network.name}
86+
active={isActive}
87+
size={"lg"}
88+
description={network.description}
89+
/>
90+
<button
91+
className={
92+
"flex items-center gap-2 dark:text-neutral-300 text-neutral-500 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 py-2 px-3 rounded-md cursor-pointer"
93+
}
94+
onClick={() => setNetworkModal(true)}
95+
>
96+
<PencilLineIcon size={18} />
97+
</button>
98+
<NetworkModal
99+
open={networkModal}
100+
setOpen={setNetworkModal}
101+
onUpdated={() => {
102+
mutate(`/networks/${network.id}`);
103+
}}
104+
network={network}
105+
/>
106+
</div>
107+
</div>
108+
109+
<div className={"flex gap-10 w-full mt-8 max-w-6xl items-start"}>
110+
<NetworkInformationCard network={network} />
111+
</div>
112+
</div>
113+
114+
<Separator />
115+
<ResourcesSection network={network} />
116+
<div className={"h-3"} />
117+
<Separator />
118+
<NetworkRoutingPeersSection network={network} />
119+
</NetworkProvider>
120+
</PageContainer>
121+
);
122+
}
123+
124+
function NetworkInformationCard({ network }: Readonly<{ network: Network }>) {
125+
const isHighlyAvailable = !!(
126+
network?.routing_peers_count && network?.routing_peers_count >= 2
127+
);
128+
129+
const disabledText = useMemo(
130+
() => (
131+
<>
132+
High availability is currently{" "}
133+
<span className={"text-yellow-400 font-medium"}>inactive</span> for this
134+
network.
135+
</>
136+
),
137+
[],
138+
);
139+
140+
const enabledText = useMemo(
141+
() => (
142+
<>
143+
High availability is{" "}
144+
<span className={"text-green-500 font-medium"}>active</span> for this
145+
network.
146+
</>
147+
),
148+
[],
149+
);
150+
151+
const policyCount = network.policies?.length ?? 0;
152+
153+
return (
154+
<Card>
155+
<Card.List>
156+
<Card.ListItem
157+
tooltip={false}
158+
label={
159+
<>
160+
<ServerIcon size={16} />
161+
High Availability
162+
</>
163+
}
164+
value={
165+
<FullTooltip
166+
interactive={false}
167+
content={
168+
<div className={"max-w-xs text-xs"}>
169+
{isHighlyAvailable ? enabledText : disabledText}
170+
{isHighlyAvailable ? (
171+
<div className={"inline-flex mt-2"}>
172+
You can add more routing peers to increase the
173+
availability of this network.
174+
</div>
175+
) : (
176+
<div className={"inline-flex mt-2"}>
177+
Go ahead and add more routing peers or groups with routing
178+
peers to enable high availability for this network.
179+
</div>
180+
)}
181+
</div>
182+
}
183+
>
184+
<div
185+
className={cn(
186+
"flex gap-2.5 items-center text-nb-gray-300 text-sm cursor-help",
187+
)}
188+
>
189+
<span
190+
className={cn(
191+
"h-2 w-2 rounded-full",
192+
!isHighlyAvailable ? "bg-yellow-400" : "bg-green-500",
193+
)}
194+
></span>
195+
{isHighlyAvailable ? "Active" : "Inactive"}
196+
<HelpCircle size={12} />
197+
</div>
198+
</FullTooltip>
199+
}
200+
/>
201+
<Card.ListItem
202+
tooltip={false}
203+
label={
204+
policyCount > 0 ? (
205+
<>
206+
<ShieldCheckIcon size={16} className={"text-green-500"} />
207+
{policyCount}{" "}
208+
{policyCount === 1 ? "Active Policy" : "Active Policies"}
209+
</>
210+
) : (
211+
<>
212+
<ShieldXIcon size={16} className={"text-red-500"} />
213+
No Active Policies
214+
</>
215+
)
216+
}
217+
value={
218+
policyCount > 0 ? (
219+
<InlineLink href={"/access-control"}>
220+
Go to Policies
221+
<ArrowUpRightIcon size={14} />
222+
</InlineLink>
223+
) : null
224+
}
225+
/>
226+
</Card.List>
227+
</Card>
228+
);
229+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { globalMetaTitle } from "@utils/meta";
2+
import type { Metadata } from "next";
3+
import BlankLayout from "@/layouts/BlankLayout";
4+
5+
export const metadata: Metadata = {
6+
title: `Networks - ${globalMetaTitle}`,
7+
};
8+
export default BlankLayout;

0 commit comments

Comments
 (0)