Compare commits

..
Author SHA1 Message Date
Eduard Gert 5e8048dd20 Push to docker 2024-04-03 13:35:39 +02:00
Eduard Gert 97255dd882 Add logging 2024-04-03 13:33:58 +02:00
205 changed files with 2405 additions and 8309 deletions

No files matched your search

+2 -1
View File
@@ -4,6 +4,7 @@ on:
branches: branches:
- "feature/**" - "feature/**"
- main - main
- debug
tags: tags:
- "**" - "**"
pull_request: pull_request:
@@ -55,7 +56,7 @@ jobs:
with: with:
context: . context: .
file: docker/Dockerfile file: docker/Dockerfile
push: ${{ github.event_name != 'pull_request' }} push: true
platforms: linux/amd64,linux/arm64,linux/arm platforms: linux/amd64,linux/arm64,linux/arm
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
+1 -2
View File
@@ -12,5 +12,4 @@ jobs:
uses: codespell-project/actions-codespell@v2 uses: codespell-project/actions-codespell@v2
with: with:
only_warn: 1 only_warn: 1
skip: package-lock.json,*.svg skip: package-lock.json,*.svg
ignore_words_list: mappin, allTime
+1 -2
View File
@@ -12,6 +12,5 @@
"tokenSource": "$NETBIRD_TOKEN_SOURCE", "tokenSource": "$NETBIRD_TOKEN_SOURCE",
"dragQueryParams": "$NETBIRD_DRAG_QUERY_PARAMS", "dragQueryParams": "$NETBIRD_DRAG_QUERY_PARAMS",
"hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID", "hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID",
"googleAnalyticsID": "$NETBIRD_GOOGLE_ANALYTICS_ID", "googleAnalyticsID": "$NETBIRD_GOOGLE_ANALYTICS_ID"
"authServiceUrl": "$NETBIRD_AUTH_SERVICE_URL"
} }
-3
View File
@@ -5,9 +5,6 @@ const nextConfig = {
unoptimized: true, unoptimized: true,
}, },
reactStrictMode: false, reactStrictMode: false,
env: {
APP_ENV: process.env.APP_ENV || "production",
},
}; };
module.exports = nextConfig; module.exports = nextConfig;
+95 -439
View File
File diff suppressed because it is too large. Load diff
+3 -6
View File
@@ -13,7 +13,7 @@
"cypress:open": "cypress open" "cypress:open": "cypress open"
}, },
"dependencies": { "dependencies": {
"@axa-fr/react-oidc": "^7.22.18", "@axa-fr/react-oidc": "^5.14.0",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",
@@ -22,9 +22,8 @@
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
@@ -38,7 +37,6 @@
"@types/node": "20.10.6", "@types/node": "20.10.6",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"@types/react-window": "^1.8.8",
"autoprefixer": "^10", "autoprefixer": "^10",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
@@ -55,7 +53,7 @@
"framer-motion": "^10.16.4", "framer-motion": "^10.16.4",
"ip-cidr": "^3.1.0", "ip-cidr": "^3.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.383.0", "lucide-react": "^0.287.0",
"next": "13.5.5", "next": "13.5.5",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"punycode": "^2.3.1", "punycode": "^2.3.1",
@@ -69,7 +67,6 @@
"react-jwt": "^1.2.0", "react-jwt": "^1.2.0",
"react-loading-skeleton": "^3.3.1", "react-loading-skeleton": "^3.3.1",
"react-responsive": "^9.0.2", "react-responsive": "^9.0.2",
"react-virtuoso": "^4.9.0",
"swr": "^2.2.4", "swr": "^2.2.4",
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
+7 -10
View File
@@ -5,7 +5,6 @@ import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable"; import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { usePortalElement } from "@hooks/usePortalElement";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense } from "react"; import React, { lazy, Suspense } from "react";
@@ -21,9 +20,6 @@ const AccessControlTable = lazy(
export default function AccessControlPage() { export default function AccessControlPage() {
const { data: policies, isLoading } = useFetchApi<Policy[]>("/policies"); const { data: policies, isLoading } = useFetchApi<Policy[]>("/policies");
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
return ( return (
<PageContainer> <PageContainer>
<GroupsProvider> <GroupsProvider>
@@ -35,7 +31,12 @@ export default function AccessControlPage() {
icon={<AccessControlIcon size={14} />} icon={<AccessControlIcon size={14} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Access Control Policies</h1>
<h1>
{policies && policies.length > 1
? `${policies.length} Access Control Policies`
: "Access Control Policies"}
</h1>
<Paragraph> <Paragraph>
Create rules to manage access in your network and define what peers Create rules to manage access in your network and define what peers
can connect. can connect.
@@ -56,11 +57,7 @@ export default function AccessControlPage() {
<RestrictedAccess page={"Access Control"}> <RestrictedAccess page={"Access Control"}>
<PoliciesProvider> <PoliciesProvider>
<Suspense fallback={<SkeletonTable />}> <Suspense fallback={<SkeletonTable />}>
<AccessControlTable <AccessControlTable isLoading={isLoading} policies={policies} />
isLoading={isLoading}
policies={policies}
headingTarget={portalTarget}
/>
</Suspense> </Suspense>
</PoliciesProvider> </PoliciesProvider>
</RestrictedAccess> </RestrictedAccess>
+6 -10
View File
@@ -4,7 +4,6 @@ import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink"; import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { usePortalElement } from "@hooks/usePortalElement";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import React from "react"; import React from "react";
@@ -16,9 +15,6 @@ import ActivityTable from "@/modules/activity/ActivityTable";
export default function Activity() { export default function Activity() {
const { data: events, isLoading } = useFetchApi<ActivityEvent[]>("/events"); const { data: events, isLoading } = useFetchApi<ActivityEvent[]>("/events");
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
return ( return (
<PageContainer> <PageContainer>
<div className={"p-default py-6"}> <div className={"p-default py-6"}>
@@ -29,7 +25,11 @@ export default function Activity() {
icon={<ActivityIcon size={13} />} icon={<ActivityIcon size={13} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Activity Events</h1> <h1>
{events && events.length > 1
? `${events.length} Activity Events`
: "Activity Events"}
</h1>
<Paragraph> <Paragraph>
Here you can see all the account and network activity events. Here you can see all the account and network activity events.
</Paragraph> </Paragraph>
@@ -48,11 +48,7 @@ export default function Activity() {
</Paragraph> </Paragraph>
</div> </div>
<RestrictedAccess page={"Activity"}> <RestrictedAccess page={"Activity"}>
<ActivityTable <ActivityTable events={events} isLoading={isLoading} />
events={events}
isLoading={isLoading}
headingTarget={portalTarget}
/>
</RestrictedAccess> </RestrictedAccess>
</PageContainer> </PageContainer>
); );
+5 -6
View File
@@ -5,7 +5,6 @@ import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable"; import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { usePortalElement } from "@hooks/usePortalElement";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon, ServerIcon } from "lucide-react"; import { ExternalLinkIcon, ServerIcon } from "lucide-react";
import React, { lazy, Suspense } from "react"; import React, { lazy, Suspense } from "react";
@@ -21,9 +20,6 @@ export default function NameServers() {
const { data: nameserverGroups, isLoading } = const { data: nameserverGroups, isLoading } =
useFetchApi<NameserverGroup[]>("/dns/nameservers"); useFetchApi<NameserverGroup[]>("/dns/nameservers");
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
return ( return (
<PageContainer> <PageContainer>
<div className={"p-default py-6"}> <div className={"p-default py-6"}>
@@ -40,7 +36,11 @@ export default function NameServers() {
icon={<ServerIcon size={13} />} icon={<ServerIcon size={13} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Nameservers</h1> <h1>
{nameserverGroups && nameserverGroups.length > 1
? `${nameserverGroups.length} Nameservers`
: "Nameservers"}
</h1>
<Paragraph> <Paragraph>
Add nameservers for domain name resolution in your NetBird network. Add nameservers for domain name resolution in your NetBird network.
</Paragraph> </Paragraph>
@@ -62,7 +62,6 @@ export default function NameServers() {
<NameserverGroupTable <NameserverGroupTable
nameserverGroups={nameserverGroups} nameserverGroups={nameserverGroups}
isLoading={isLoading} isLoading={isLoading}
headingTarget={portalTarget}
/> />
</Suspense> </Suspense>
</RestrictedAccess> </RestrictedAccess>
+7 -22
View File
@@ -14,24 +14,17 @@ import { IconSettings2 } from "@tabler/icons-react";
import useFetchApi, { useApiCall } from "@utils/api"; import useFetchApi, { useApiCall } from "@utils/api";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import React from "react"; import React from "react";
import Skeleton from "react-loading-skeleton";
import { useSWRConfig } from "swr"; import { useSWRConfig } from "swr";
import DNSIcon from "@/assets/icons/DNSIcon"; import DNSIcon from "@/assets/icons/DNSIcon";
import { useHasChanges } from "@/hooks/useHasChanges"; import { useHasChanges } from "@/hooks/useHasChanges";
import { Group } from "@/interfaces/Group";
import { NameserverSettings } from "@/interfaces/NameserverSettings"; import { NameserverSettings } from "@/interfaces/NameserverSettings";
import PageContainer from "@/layouts/PageContainer"; import PageContainer from "@/layouts/PageContainer";
import useGroupHelper from "@/modules/groups/useGroupHelper"; import useGroupHelper from "@/modules/groups/useGroupHelper";
import { useGroupIdsToGroups } from "@/modules/groups/useGroupIdsToGroups";
export default function NameServerSettings() { export default function NameServerSettings() {
const { data: settings, isLoading } = const { data: settings, isLoading } =
useFetchApi<NameserverSettings>("/dns/settings"); useFetchApi<NameserverSettings>("/dns/settings");
const initialDNSGroups = useGroupIdsToGroups(
settings?.disabled_management_groups,
);
return ( return (
<PageContainer> <PageContainer>
<div className={"p-default py-6"}> <div className={"p-default py-6"}>
@@ -62,16 +55,10 @@ export default function NameServerSettings() {
in our documentation. in our documentation.
</Paragraph> </Paragraph>
<RestrictedAccess page={"DNS Settings"}> <RestrictedAccess page={"DNS Settings"}>
{!isLoading && initialDNSGroups !== undefined ? ( {!isLoading && (
<SettingDisabledManagementGroups initialGroups={initialDNSGroups} /> <SettingDisabledManagementGroups
) : ( initial={settings?.disabled_management_groups}
<div> />
<Skeleton
width={"100%"}
className={"mt-8 max-w-xl"}
height={240}
/>
</div>
)} )}
</RestrictedAccess> </RestrictedAccess>
</div> </div>
@@ -80,16 +67,16 @@ export default function NameServerSettings() {
} }
const SettingDisabledManagementGroups = ({ const SettingDisabledManagementGroups = ({
initialGroups, initial,
}: { }: {
initialGroups: Group[]; initial: string[] | undefined;
}) => { }) => {
const settingRequest = useApiCall<NameserverSettings>("/dns/settings"); const settingRequest = useApiCall<NameserverSettings>("/dns/settings");
const { mutate } = useSWRConfig(); const { mutate } = useSWRConfig();
const [selectedGroups, setSelectedGroups, { save: saveGroups }] = const [selectedGroups, setSelectedGroups, { save: saveGroups }] =
useGroupHelper({ useGroupHelper({
initial: initialGroups, initial: initial || [],
}); });
const { hasChanges, updateRef: updateChangesRef } = useHasChanges([ const { hasChanges, updateRef: updateChangesRef } = useHasChanges([
@@ -121,7 +108,6 @@ const SettingDisabledManagementGroups = ({
Peers in these groups will require manual domain name resolution Peers in these groups will require manual domain name resolution
</HelpText> </HelpText>
<PeerGroupSelector <PeerGroupSelector
dataCy={"dns-groups-selector"}
onChange={setSelectedGroups} onChange={setSelectedGroups}
values={selectedGroups} values={selectedGroups}
/> />
@@ -136,7 +122,6 @@ const SettingDisabledManagementGroups = ({
size={"sm"} size={"sm"}
onClick={saveSettings} onClick={saveSettings}
disabled={!hasChanges} disabled={!hasChanges}
data-cy={"save-changes"}
> >
Save Changes Save Changes
</Button> </Button>
+5 -6
View File
@@ -5,7 +5,6 @@ import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable"; import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { usePortalElement } from "@hooks/usePortalElement";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense } from "react"; import React, { lazy, Suspense } from "react";
@@ -24,9 +23,6 @@ export default function NetworkRoutes() {
const { data: routes, isLoading } = useFetchApi<Route[]>("/routes"); const { data: routes, isLoading } = useFetchApi<Route[]>("/routes");
const groupedRoutes = useGroupedRoutes({ routes }); const groupedRoutes = useGroupedRoutes({ routes });
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
return ( return (
<PageContainer> <PageContainer>
<RoutesProvider> <RoutesProvider>
@@ -39,7 +35,11 @@ export default function NetworkRoutes() {
icon={<NetworkRoutesIcon size={13} />} icon={<NetworkRoutesIcon size={13} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Network Routes</h1> <h1>
{groupedRoutes && groupedRoutes.length > 1
? `${groupedRoutes.length} Network Routes`
: "Network Routes"}
</h1>
<Paragraph> <Paragraph>
Network routes allow you to access other networks like LANs and Network routes allow you to access other networks like LANs and
VPCs without installing NetBird on every resource. VPCs without installing NetBird on every resource.
@@ -65,7 +65,6 @@ export default function NetworkRoutes() {
isLoading={isLoading} isLoading={isLoading}
groupedRoutes={groupedRoutes} groupedRoutes={groupedRoutes}
routes={routes} routes={routes}
headingTarget={portalTarget}
/> />
</Suspense> </Suspense>
</RestrictedAccess> </RestrictedAccess>
+28 -23
View File
@@ -23,8 +23,6 @@ import Separator from "@components/Separator";
import FullScreenLoading from "@components/ui/FullScreenLoading"; import FullScreenLoading from "@components/ui/FullScreenLoading";
import LoginExpiredBadge from "@components/ui/LoginExpiredBadge"; import LoginExpiredBadge from "@components/ui/LoginExpiredBadge";
import TextWithTooltip from "@components/ui/TextWithTooltip"; import TextWithTooltip from "@components/ui/TextWithTooltip";
import { getOperatingSystem } from "@hooks/useOperatingSystem";
import useRedirect from "@hooks/useRedirect";
import { IconCloudLock, IconInfoCircle } from "@tabler/icons-react"; import { IconCloudLock, IconInfoCircle } from "@tabler/icons-react";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import dayjs from "dayjs"; import dayjs from "dayjs";
@@ -55,22 +53,20 @@ import PeerProvider, { usePeer } from "@/contexts/PeerProvider";
import RoutesProvider from "@/contexts/RoutesProvider"; import RoutesProvider from "@/contexts/RoutesProvider";
import { useLoggedInUser } from "@/contexts/UsersProvider"; import { useLoggedInUser } from "@/contexts/UsersProvider";
import { useHasChanges } from "@/hooks/useHasChanges"; import { useHasChanges } from "@/hooks/useHasChanges";
import { getOperatingSystem } from "@/hooks/useOperatingSystem";
import { OperatingSystem } from "@/interfaces/OperatingSystem"; import { OperatingSystem } from "@/interfaces/OperatingSystem";
import type { Peer } from "@/interfaces/Peer"; import type { Peer } from "@/interfaces/Peer";
import PageContainer from "@/layouts/PageContainer"; import PageContainer from "@/layouts/PageContainer";
import useGroupHelper from "@/modules/groups/useGroupHelper"; import useGroupHelper from "@/modules/groups/useGroupHelper";
import { AccessiblePeersSection } from "@/modules/peer/AccessiblePeersSection"; import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton";
import { PeerNetworkRoutesSection } from "@/modules/peer/PeerNetworkRoutesSection"; import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
export default function PeerPage() { export default function PeerPage() {
const queryParameter = useSearchParams(); const queryParameter = useSearchParams();
const peerId = queryParameter.get("id"); const peerId = queryParameter.get("id");
const { data: peer, isLoading } = useFetchApi<Peer>("/peers/" + peerId, true); const { data: peer } = useFetchApi<Peer>("/peers/" + peerId);
return peer ? (
useRedirect("/peers", false, !peerId); <PeerProvider peer={peer}>
return peer && !isLoading ? (
<PeerProvider peer={peer} key={peerId}>
<PeerOverview /> <PeerOverview />
</PeerProvider> </PeerProvider>
) : ( ) : (
@@ -324,7 +320,6 @@ function PeerOverview() {
disabled={isUser} disabled={isUser}
onChange={setSelectedGroups} onChange={setSelectedGroups}
values={selectedGroups} values={selectedGroups}
hideAllGroup={true}
peer={peer} peer={peer}
/> />
</FullTooltip> </FullTooltip>
@@ -333,19 +328,29 @@ function PeerOverview() {
</div> </div>
</div> </div>
{isLinux && !isUser ? ( <Separator />
<>
<Separator />
<PeerNetworkRoutesSection peer={peer} />
</>
) : null}
{peer?.id && ( {isLinux && !isUser ? (
<> <div className={"px-8 py-6"}>
<Separator /> <div className={"max-w-6xl"}>
<AccessiblePeersSection peerID={peer.id} /> <div className={"flex justify-between items-center"}>
</> <div>
)} <h2>Network Routes</h2>
<Paragraph>
Access other networks without installing NetBird on every
resource.
</Paragraph>
</div>
<div className={"inline-flex gap-4 justify-end"}>
<div>
<AddRouteDropdownButton />
</div>
</div>
</div>
<PeerRoutesTable peer={peer} />
</div>
</div>
) : null}
</RoutesProvider> </RoutesProvider>
</PageContainer> </PageContainer>
); );
+15 -16
View File
@@ -4,12 +4,13 @@ import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink"; import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable"; import SkeletonTable from "@components/skeletons/SkeletonTable";
import { usePortalElement } from "@hooks/usePortalElement"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense } from "react"; import React, { lazy, Suspense, useEffect } from "react";
import PeerIcon from "@/assets/icons/PeerIcon"; import PeerIcon from "@/assets/icons/PeerIcon";
import PeersProvider, { usePeers } from "@/contexts/PeersProvider"; import { useGroups } from "@/contexts/GroupsProvider";
import { useLoggedInUser, useUsers } from "@/contexts/UsersProvider"; import { useLoggedInUser, useUsers } from "@/contexts/UsersProvider";
import { Peer } from "@/interfaces/Peer";
import PageContainer from "@/layouts/PageContainer"; import PageContainer from "@/layouts/PageContainer";
import { SetupModalContent } from "@/modules/setup-netbird-modal/SetupModal"; import { SetupModalContent } from "@/modules/setup-netbird-modal/SetupModal";
@@ -20,22 +21,24 @@ export default function Peers() {
return ( return (
<PageContainer> <PageContainer>
{permission.dashboard_view === "blocked" ? ( {permission?.dashboard_view === "blocked" ? (
<PeersBlockedView /> <PeersBlockedView />
) : ( ) : (
<PeersProvider> <PeersView />
<PeersView />
</PeersProvider>
)} )}
</PageContainer> </PageContainer>
); );
} }
function PeersView() { function PeersView() {
const { peers, isLoading } = usePeers(); const { data: peers, isLoading } = useFetchApi<Peer[]>("/peers");
const { users } = useUsers(); const { users } = useUsers();
const { ref: headingRef, portalTarget } = const { refresh } = useGroups();
usePortalElement<HTMLHeadingElement>();
useEffect(() => {
refresh();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const peersWithUser = peers?.map((peer) => { const peersWithUser = peers?.map((peer) => {
if (!users) return peer; if (!users) return peer;
@@ -55,7 +58,7 @@ function PeersView() {
icon={<PeerIcon size={13} />} icon={<PeerIcon size={13} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Peers</h1> <h1>{peers && peers.length > 1 ? `${peers.length} Peers` : "Peers"}</h1>
<Paragraph> <Paragraph>
A list of all machines and devices connected to your private network. A list of all machines and devices connected to your private network.
Use this view to manage peers. Use this view to manage peers.
@@ -73,11 +76,7 @@ function PeersView() {
</Paragraph> </Paragraph>
</div> </div>
<Suspense fallback={<SkeletonTable />}> <Suspense fallback={<SkeletonTable />}>
<PeersTable <PeersTable isLoading={isLoading} peers={peersWithUser} />
isLoading={isLoading}
peers={peersWithUser}
headingTarget={portalTarget}
/>
</Suspense> </Suspense>
</> </>
); );
+6 -10
View File
@@ -5,7 +5,6 @@ import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable"; import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { usePortalElement } from "@hooks/usePortalElement";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon, ShieldCheck } from "lucide-react"; import { ExternalLinkIcon, ShieldCheck } from "lucide-react";
import React, { lazy, Suspense } from "react"; import React, { lazy, Suspense } from "react";
@@ -22,9 +21,6 @@ export default function PostureChecksPage() {
const { data: postureChecks, isLoading } = const { data: postureChecks, isLoading } =
useFetchApi<PostureCheck[]>("/posture-checks"); useFetchApi<PostureCheck[]>("/posture-checks");
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
return ( return (
<PageContainer> <PageContainer>
<GroupsProvider> <GroupsProvider>
@@ -42,16 +38,17 @@ export default function PostureChecksPage() {
icon={<ShieldCheck size={15} />} icon={<ShieldCheck size={15} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Posture Checks</h1> <h1>
{postureChecks && postureChecks.length > 1
? `${postureChecks.length} Posture Checks`
: "Posture Checks"}
</h1>
<Paragraph> <Paragraph>
Use posture checks to further restrict access in your network. Use posture checks to further restrict access in your network.
</Paragraph> </Paragraph>
<Paragraph> <Paragraph>
Learn more about Learn more about
<InlineLink <InlineLink href={"https://docs.netbird.io/how-to/manage-posture-checks"} target={"_blank"}>
href={"https://docs.netbird.io/how-to/manage-posture-checks"}
target={"_blank"}
>
Posture Checks Posture Checks
<ExternalLinkIcon size={12} /> <ExternalLinkIcon size={12} />
</InlineLink> </InlineLink>
@@ -63,7 +60,6 @@ export default function PostureChecksPage() {
<PoliciesProvider> <PoliciesProvider>
<Suspense fallback={<SkeletonTable />}> <Suspense fallback={<SkeletonTable />}>
<PostureCheckTable <PostureCheckTable
headingTarget={portalTarget}
isLoading={isLoading} isLoading={isLoading}
postureChecks={postureChecks} postureChecks={postureChecks}
/> />
+1 -5
View File
@@ -11,7 +11,6 @@ import {
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useLoggedInUser } from "@/contexts/UsersProvider"; import { useLoggedInUser } from "@/contexts/UsersProvider";
import { Role } from "@/interfaces/User";
import PageContainer from "@/layouts/PageContainer"; import PageContainer from "@/layouts/PageContainer";
import { useAccount } from "@/modules/account/useAccount"; import { useAccount } from "@/modules/account/useAccount";
import AuthenticationTab from "@/modules/settings/AuthenticationTab"; import AuthenticationTab from "@/modules/settings/AuthenticationTab";
@@ -53,10 +52,7 @@ export default function NetBirdSettings() {
Danger zone Danger zone
</VerticalTabs.Trigger> </VerticalTabs.Trigger>
</VerticalTabs.List> </VerticalTabs.List>
<RestrictedAccess <RestrictedAccess page={"Settings"}>
page={"Settings"}
allow={[Role.Admin, Role.Owner, Role.BillingAdmin]}
>
<div className={"border-l border-nb-gray-930 w-full"}> <div className={"border-l border-nb-gray-930 w-full"}>
{account && <AuthenticationTab account={account} />} {account && <AuthenticationTab account={account} />}
{account && <PermissionsTab account={account} />} {account && <PermissionsTab account={account} />}
+16 -22
View File
@@ -5,10 +5,9 @@ import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable"; import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { usePortalElement } from "@hooks/usePortalElement";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense, useMemo } from "react"; import React, { lazy, Suspense } from "react";
import SetupKeysIcon from "@/assets/icons/SetupKeysIcon"; import SetupKeysIcon from "@/assets/icons/SetupKeysIcon";
import { useGroups } from "@/contexts/GroupsProvider"; import { useGroups } from "@/contexts/GroupsProvider";
import { Group } from "@/interfaces/Group"; import { Group } from "@/interfaces/Group";
@@ -23,24 +22,16 @@ export default function SetupKeys() {
const { data: setupKeys, isLoading } = useFetchApi<SetupKey[]>("/setup-keys"); const { data: setupKeys, isLoading } = useFetchApi<SetupKey[]>("/setup-keys");
const { groups } = useGroups(); const { groups } = useGroups();
const setupKeysWithGroups = useMemo(() => { const setupKeysWithGroups = setupKeys?.map((setupKey) => {
if (!setupKeys) return []; if (!setupKey.auto_groups) return setupKey;
return setupKeys?.map((setupKey) => { if (!groups) return setupKey;
if (!setupKey.auto_groups) return setupKey; return {
if (!groups) return setupKey; ...setupKey,
return { groups: setupKey.auto_groups.map((group) => {
...setupKey, return groups.find((g) => g.id === group) || undefined;
groups: setupKey.auto_groups }) as Group[] | undefined,
?.map((group) => { };
return groups.find((g) => g.id === group) || undefined; });
})
.filter((group) => group !== undefined) as Group[],
};
});
}, [setupKeys, groups]);
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
return ( return (
<PageContainer> <PageContainer>
@@ -52,7 +43,11 @@ export default function SetupKeys() {
icon={<SetupKeysIcon size={13} />} icon={<SetupKeysIcon size={13} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Setup Keys</h1> <h1>
{setupKeys && setupKeys.length > 1
? `${setupKeys.length} Setup Keys`
: "Setup Keys"}
</h1>
<Paragraph> <Paragraph>
Setup keys are pre-authentication keys that allow to register new Setup keys are pre-authentication keys that allow to register new
machines in your network. machines in your network.
@@ -74,7 +69,6 @@ export default function SetupKeys() {
<RestrictedAccess page={"Setup Keys"}> <RestrictedAccess page={"Setup Keys"}>
<Suspense fallback={<SkeletonTable />}> <Suspense fallback={<SkeletonTable />}>
<SetupKeysTable <SetupKeysTable
headingTarget={portalTarget}
setupKeys={setupKeysWithGroups} setupKeys={setupKeysWithGroups}
isLoading={isLoading} isLoading={isLoading}
/> />
@@ -5,7 +5,6 @@ import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable"; import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { usePortalElement } from "@hooks/usePortalElement";
import { IconSettings2 } from "@tabler/icons-react"; import { IconSettings2 } from "@tabler/icons-react";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
@@ -23,9 +22,6 @@ export default function ServiceUsers() {
"/users?service_user=true", "/users?service_user=true",
); );
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
return ( return (
<PageContainer> <PageContainer>
<div className={"p-default py-6"}> <div className={"p-default py-6"}>
@@ -42,7 +38,11 @@ export default function ServiceUsers() {
icon={<IconSettings2 size={17} />} icon={<IconSettings2 size={17} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Service Users</h1> <h1>
{users && users.length > 1
? `${users.length} Service Users`
: "Service Users"}
</h1>
<Paragraph> <Paragraph>
Use service users to create API tokens and avoid losing automated Use service users to create API tokens and avoid losing automated
access. access.
@@ -61,11 +61,7 @@ export default function ServiceUsers() {
</div> </div>
<RestrictedAccess page={"Service Users"}> <RestrictedAccess page={"Service Users"}>
<Suspense fallback={<SkeletonTable />}> <Suspense fallback={<SkeletonTable />}>
<ServiceUsersTable <ServiceUsersTable users={users} isLoading={isLoading} />
users={users}
isLoading={isLoading}
headingTarget={portalTarget}
/>
</Suspense> </Suspense>
</RestrictedAccess> </RestrictedAccess>
</PageContainer> </PageContainer>
+15 -34
View File
@@ -10,7 +10,6 @@ import Paragraph from "@components/Paragraph";
import { PeerGroupSelector } from "@components/PeerGroupSelector"; import { PeerGroupSelector } from "@components/PeerGroupSelector";
import Separator from "@components/Separator"; import Separator from "@components/Separator";
import FullScreenLoading from "@components/ui/FullScreenLoading"; import FullScreenLoading from "@components/ui/FullScreenLoading";
import useRedirect from "@hooks/useRedirect";
import { IconCirclePlus, IconSettings2 } from "@tabler/icons-react"; import { IconCirclePlus, IconSettings2 } from "@tabler/icons-react";
import useFetchApi, { useApiCall } from "@utils/api"; import useFetchApi, { useApiCall } from "@utils/api";
import { generateColorFromString } from "@utils/helpers"; import { generateColorFromString } from "@utils/helpers";
@@ -22,13 +21,11 @@ import { useSWRConfig } from "swr";
import TeamIcon from "@/assets/icons/TeamIcon"; import TeamIcon from "@/assets/icons/TeamIcon";
import { useLoggedInUser } from "@/contexts/UsersProvider"; import { useLoggedInUser } from "@/contexts/UsersProvider";
import { useHasChanges } from "@/hooks/useHasChanges"; import { useHasChanges } from "@/hooks/useHasChanges";
import { Group } from "@/interfaces/Group";
import { Role, User } from "@/interfaces/User"; import { Role, User } from "@/interfaces/User";
import PageContainer from "@/layouts/PageContainer"; import PageContainer from "@/layouts/PageContainer";
import AccessTokensTable from "@/modules/access-tokens/AccessTokensTable"; import AccessTokensTable from "@/modules/access-tokens/AccessTokensTable";
import CreateAccessTokenModal from "@/modules/access-tokens/CreateAccessTokenModal"; import CreateAccessTokenModal from "@/modules/access-tokens/CreateAccessTokenModal";
import useGroupHelper from "@/modules/groups/useGroupHelper"; import useGroupHelper from "@/modules/groups/useGroupHelper";
import { useGroupIdsToGroups } from "@/modules/groups/useGroupIdsToGroups";
import UserBlockCell from "@/modules/users/table-cells/UserBlockCell"; import UserBlockCell from "@/modules/users/table-cells/UserBlockCell";
import UserStatusCell from "@/modules/users/table-cells/UserStatusCell"; import UserStatusCell from "@/modules/users/table-cells/UserStatusCell";
import { UserRoleSelector } from "@/modules/users/UserRoleSelector"; import { UserRoleSelector } from "@/modules/users/UserRoleSelector";
@@ -45,12 +42,8 @@ export default function UserPage() {
return users?.find((u) => u.id === userId); return users?.find((u) => u.id === userId);
}, [users, userId]); }, [users, userId]);
useRedirect("/team/users", false, !userId); return !isLoading && user ? (
<UserOverview user={user} />
const userGroups = useGroupIdsToGroups(user?.auto_groups);
return !isLoading && user && userGroups !== undefined ? (
<UserOverview user={user} initialGroups={userGroups} />
) : ( ) : (
<FullScreenLoading /> <FullScreenLoading />
); );
@@ -58,16 +51,16 @@ export default function UserPage() {
type Props = { type Props = {
user: User; user: User;
initialGroups: Group[];
}; };
function UserOverview({ user, initialGroups }: Readonly<Props>) { function UserOverview({ user }: Props) {
const router = useRouter(); const router = useRouter();
const userRequest = useApiCall<User>("/users"); const userRequest = useApiCall<User>("/users");
const { mutate } = useSWRConfig(); const { mutate } = useSWRConfig();
const { loggedInUser, isOwnerOrAdmin, isUser } = useLoggedInUser(); const { loggedInUser, isOwnerOrAdmin, isUser } = useLoggedInUser();
const isLoggedInUser = loggedInUser ? loggedInUser?.id === user.id : false; const isLoggedInUser = loggedInUser ? loggedInUser?.id === user.id : false;
const initialGroups = user.auto_groups;
const [selectedGroups, setSelectedGroups, { save: saveGroups }] = const [selectedGroups, setSelectedGroups, { save: saveGroups }] =
useGroupHelper({ useGroupHelper({
initial: initialGroups, initial: initialGroups,
@@ -184,7 +177,6 @@ function UserOverview({ user, initialGroups }: Readonly<Props>) {
className={"w-full"} className={"w-full"}
disabled={!hasChanges} disabled={!hasChanges}
onClick={save} onClick={save}
data-cy={"save-changes"}
> >
Save Changes Save Changes
</Button> </Button>
@@ -192,7 +184,7 @@ function UserOverview({ user, initialGroups }: Readonly<Props>) {
)} )}
</div> </div>
<div className={"flex gap-10 w-full mt-8 max-w-6xl items-start"}> <div className={"flex gap-10 w-full mt-8 max-w-6xl"}>
<UserInformationCard user={user} /> <UserInformationCard user={user} />
<div className={"flex flex-col gap-8 w-1/2 "}> <div className={"flex flex-col gap-8 w-1/2 "}>
{!user.is_service_user && ( {!user.is_service_user && (
@@ -205,8 +197,6 @@ function UserOverview({ user, initialGroups }: Readonly<Props>) {
disabled={isUser} disabled={isUser}
onChange={setSelectedGroups} onChange={setSelectedGroups}
values={selectedGroups} values={selectedGroups}
hideAllGroup={true}
dataCy={"user-group-selector"}
/> />
</div> </div>
)} )}
@@ -221,8 +211,6 @@ function UserOverview({ user, initialGroups }: Readonly<Props>) {
<UserRoleSelector <UserRoleSelector
value={role} value={role}
onChange={setRole} onChange={setRole}
hideOwner={user.is_service_user}
currentUser={user}
disabled={ disabled={
isLoggedInUser || isLoggedInUser ||
!isOwnerOrAdmin || !isOwnerOrAdmin ||
@@ -250,10 +238,7 @@ function UserOverview({ user, initialGroups }: Readonly<Props>) {
<div className={"inline-flex gap-4 justify-end"}> <div className={"inline-flex gap-4 justify-end"}>
<div> <div>
<CreateAccessTokenModal user={user}> <CreateAccessTokenModal user={user}>
<Button <Button variant={"primary"}>
variant={"primary"}
data-cy={"access-token-open-modal"}
>
<IconCirclePlus size={16} /> <IconCirclePlus size={16} />
Create Access Token Create Access Token
</Button> </Button>
@@ -302,7 +287,6 @@ function UserInformationCard({ user }: { user: User }) {
)} )}
<Card.ListItem <Card.ListItem
tooltip={false}
label={ label={
<> <>
<GalleryHorizontalEnd size={16} /> <GalleryHorizontalEnd size={16} />
@@ -314,18 +298,15 @@ function UserInformationCard({ user }: { user: User }) {
{!isServiceUser && ( {!isServiceUser && (
<> <>
{!user.is_current && user.role != Role.Owner && ( <Card.ListItem
<Card.ListItem label={
label={ <>
<> <Ban size={16} />
<Ban size={16} /> Block User
Block User </>
</> }
} value={<UserBlockCell user={user} isUserPage={true} />}
value={<UserBlockCell user={user} isUserPage={true} />} />
/>
)}
<Card.ListItem <Card.ListItem
label={ label={
<> <>
+2 -12
View File
@@ -5,26 +5,20 @@ import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph"; import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable"; import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { usePortalElement } from "@hooks/usePortalElement";
import useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { ExternalLinkIcon, User2 } from "lucide-react"; import { ExternalLinkIcon, User2 } from "lucide-react";
import React, { lazy, Suspense } from "react"; import React, { lazy, Suspense } from "react";
import TeamIcon from "@/assets/icons/TeamIcon"; import TeamIcon from "@/assets/icons/TeamIcon";
import { useGroups } from "@/contexts/GroupsProvider";
import { User } from "@/interfaces/User"; import { User } from "@/interfaces/User";
import PageContainer from "@/layouts/PageContainer"; import PageContainer from "@/layouts/PageContainer";
const UsersTable = lazy(() => import("@/modules/users/UsersTable")); const UsersTable = lazy(() => import("@/modules/users/UsersTable"));
export default function TeamUsers() { export default function TeamUsers() {
const { isLoading: isGroupsLoading } = useGroups();
const { data: users, isLoading } = useFetchApi<User[]>( const { data: users, isLoading } = useFetchApi<User[]>(
"/users?service_user=false", "/users?service_user=false",
); );
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
return ( return (
<PageContainer> <PageContainer>
<div className={"p-default py-6"}> <div className={"p-default py-6"}>
@@ -41,7 +35,7 @@ export default function TeamUsers() {
icon={<User2 size={16} />} icon={<User2 size={16} />}
/> />
</Breadcrumbs> </Breadcrumbs>
<h1 ref={headingRef}>Users</h1> <h1>{users && users.length > 1 ? `${users.length} Users` : "Users"}</h1>
<Paragraph> <Paragraph>
Manage users and their permissions. Same-domain email users are added Manage users and their permissions. Same-domain email users are added
automatically on first sign-in. automatically on first sign-in.
@@ -60,11 +54,7 @@ export default function TeamUsers() {
</div> </div>
<RestrictedAccess page={"Users"}> <RestrictedAccess page={"Users"}>
<Suspense fallback={<SkeletonTable />}> <Suspense fallback={<SkeletonTable />}>
<UsersTable <UsersTable users={users} isLoading={isLoading} />
users={users}
isLoading={isLoading || isGroupsLoading}
headingTarget={portalTarget}
/>
</Suspense> </Suspense>
</RestrictedAccess> </RestrictedAccess>
</PageContainer> </PageContainer>
-5
View File
@@ -68,9 +68,4 @@ p {
.stepper-bg-variant .step-circle { .stepper-bg-variant .step-circle {
@apply !border-[#1d2024]; @apply !border-[#1d2024];
}
.webkit-scroll{
-webkit-overflow-scrolling: touch;
-webkit-transform: translate3d(0, 0, 0);
} }
Loaded 20 of 205 files, more files were not shown because too many files have changed in this diff. Show more