Compare commits

...
Author SHA1 Message Date
Eduard Gert 0e553f3c83 Update redirect logic, fix exit node dropdown, fix route search 2024-06-12 16:58:10 +02:00
Eduard Gert 79164e9dd5 Add process posture check (#378)
* Add process posture check

* Add support for separate linux and mac paths
2024-06-12 16:32:10 +02:00
Eduard Gert 5caeab118b UX changes for modals and refactoring (#380) 2024-05-08 14:42:04 +02:00
Eduard Gert 3f943bb7d4 Use next/font/local instead of next/font/google (#376) 2024-04-19 17:12:56 +02:00
Eduard Gert 96b939e6cc Add changes from cloud repo to public one (#377)
* Remove unused files

* Update activity descriptions

* Update SelectDropdown

* Update redirect logic for / page

* Update HelpText.tsx

* Update wording for exit nodes
2024-04-19 17:12:37 +02:00
Eduard Gert 5e13548b81 Add better input validation for setup-keys, nameserver and routes (#373)
* Return the correct promise for errors

* Update icon

* Add better validation for routes

* Add better validation for DNS

* Add better validation for setup keys

* Merge exit nodes to input validation
2024-04-17 15:27:21 +02:00
Eduard Gert 2272a1d2a4 Add Exit Nodes (#374)
* Add exit node feature

* Fix spelling

* Hide masquerade for exit nodes

* Add exit node information to peers list

* Change exit node button, add indicator to peers table

* Add steps to route modal

* Add hook to check if peer has exit nodes

* Hide exit node indicator for regular users

* Add documentation links
2024-04-17 13:11:38 +02:00
Eduard Gert fc3da50346 Add fallbacks for setup key name & setup key group names (#370)
* Add try catch block for global search

* Add fallback for group name

* Add fallback for setup key name

* Do not load setup key modal if it's not open

* Check if auto_groups actually exists for the setup keys

* Add fallback for group names in setup keys table

* Add fallback for group names in peers table
2024-04-11 16:42:27 +02:00
Eduard Gert 6d4716cdad Remove integrations from public repo and sync changes (#369)
* Change icon size

* Remove integrations

* Add no cache header

* Add analytics event tracking

* Add small announcement improvements

* Remove peer approval setting

* Do not load countries when user has no permission

* Add tab query params to settings

* Decrease navigation font size

* Change order of providers

* Increase padding for modals

* Show page only when user is fully loaded and found

* Remove unused state

* Remove integrations page
2024-04-02 14:06:38 +02:00
amplitudes 859916b1df fix: user deletion notification (#367) 2024-04-02 12:26:45 +02:00
Eduard Gert 80ce7d21b0 Fix issue where the first users cache is not populated (#366) 2024-03-28 11:27:00 +01:00
Eduard Gert 06fdbd8ec4 Hide profile settings and announcements for blocked dashboard view (#365) 2024-03-28 10:25:21 +01:00
Eduard Gert 973cceff79 Add setting to change dashboard view for regular users (#362) 2024-03-27 16:09:58 +01:00
Eduard Gert f4a2d6fae8 Add Okta SCIM integration (#361)
* Add Okta integration (wip)

* Update okta setup dialog

* Add okta integration images

* Add error handling for 500 status codes

* Add okta integration

* Fix lint warnings

* Update azures last sync time

* Remove 'on' from step, disable copy for HTTP Header

* Update text for custom IDP
2024-03-27 15:55:56 +01:00
Eduard Gert cb922b46b7 Add 'Offline' filter to peers table (#364) 2024-03-26 20:03:24 +01:00
Eduard Gert 4c56ae704c Show peers for regular users but hide / disable actions (delete, enable ssh etc.) (#360)
* Show peers for regular users but hide / disable actions (delete, enable ssh etc.)

* Do not load countries for regular users
2024-03-21 14:21:26 +01:00
134 changed files with 2412 additions and 3945 deletions

No files matched your search

+4 -1
View File
@@ -1,4 +1,3 @@
# simple server configuration to replace nginx's default
server { server {
listen 80 default_server; listen 80 default_server;
listen [::]:80 default_server; listen [::]:80 default_server;
@@ -7,10 +6,14 @@ server {
location / { location / {
try_files $uri $uri.html $uri/ =404; try_files $uri $uri.html $uri/ =404;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
expires off;
} }
error_page 404 /404.html; error_page 404 /404.html;
location = /404.html { location = /404.html {
internal; internal;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
expires off;
} }
} }
+3
View File
@@ -5,6 +5,9 @@ const nextConfig = {
unoptimized: true, unoptimized: true,
}, },
reactStrictMode: false, reactStrictMode: false,
env: {
APP_ENV: process.env.APP_ENV || "production",
},
}; };
module.exports = nextConfig; module.exports = nextConfig;
-3
View File
@@ -5,14 +5,12 @@ 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 useFetchApi from "@utils/api"; import useFetchApi from "@utils/api";
import { isLocalDev, isNetBirdHosted } from "@utils/netbird";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import React from "react"; import React from "react";
import ActivityIcon from "@/assets/icons/ActivityIcon"; import ActivityIcon from "@/assets/icons/ActivityIcon";
import { ActivityEvent } from "@/interfaces/ActivityEvent"; import { ActivityEvent } from "@/interfaces/ActivityEvent";
import PageContainer from "@/layouts/PageContainer"; import PageContainer from "@/layouts/PageContainer";
import ActivityTable from "@/modules/activity/ActivityTable"; import ActivityTable from "@/modules/activity/ActivityTable";
import { EventStreamingCard } from "@/modules/integrations/event-streaming/EventStreamingCard";
export default function Activity() { export default function Activity() {
const { data: events, isLoading } = useFetchApi<ActivityEvent[]>("/events"); const { data: events, isLoading } = useFetchApi<ActivityEvent[]>("/events");
@@ -50,7 +48,6 @@ export default function Activity() {
</Paragraph> </Paragraph>
</div> </div>
<RestrictedAccess page={"Activity"}> <RestrictedAccess page={"Activity"}>
{(isLocalDev() || isNetBirdHosted()) && <EventStreamingCard />}
<ActivityTable events={events} isLoading={isLoading} /> <ActivityTable events={events} isLoading={isLoading} />
</RestrictedAccess> </RestrictedAccess>
</PageContainer> </PageContainer>
@@ -1,8 +0,0 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Integrations - ${globalMetaTitle}`,
};
export default BlankLayout;
-39
View File
@@ -1,39 +0,0 @@
"use client";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { VerticalTabs } from "@components/VerticalTabs";
import { FileText, FingerprintIcon } from "lucide-react";
import { useSearchParams } from "next/navigation";
import React, { useState } from "react";
import PageContainer from "@/layouts/PageContainer";
import EventStreamingTab from "@/modules/integrations/event-streaming/EventStreamingTab";
import IdentityProviderTab from "@/modules/integrations/idp-sync/IdentityProviderTab";
export default function Integrations() {
const searchParams = useSearchParams();
const currentTab = searchParams.get("tab");
const [tab, setTab] = useState(currentTab || "event-streaming");
return (
<PageContainer>
<VerticalTabs value={tab} onChange={setTab}>
<VerticalTabs.List>
<VerticalTabs.Trigger value="event-streaming">
<FileText size={14} />
Event Streaming
</VerticalTabs.Trigger>
<VerticalTabs.Trigger value="identity-provider">
<FingerprintIcon size={14} />
Identity Provider
</VerticalTabs.Trigger>
</VerticalTabs.List>
<RestrictedAccess page={"Integrations"}>
<div className={"border-l border-nb-gray-930 w-full"}>
<EventStreamingTab />
<IdentityProviderTab />
</div>
</RestrictedAccess>
</VerticalTabs>
</PageContainer>
);
}
+125 -56
View File
@@ -23,6 +23,7 @@ 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 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";
@@ -32,6 +33,7 @@ import {
FlagIcon, FlagIcon,
Globe, Globe,
History, History,
LockIcon,
MapPin, MapPin,
MonitorSmartphoneIcon, MonitorSmartphoneIcon,
NetworkIcon, NetworkIcon,
@@ -50,11 +52,14 @@ import PeerIcon from "@/assets/icons/PeerIcon";
import { useCountries } from "@/contexts/CountryProvider"; import { useCountries } from "@/contexts/CountryProvider";
import PeerProvider, { usePeer } from "@/contexts/PeerProvider"; import PeerProvider, { usePeer } from "@/contexts/PeerProvider";
import RoutesProvider from "@/contexts/RoutesProvider"; import RoutesProvider from "@/contexts/RoutesProvider";
import { useLoggedInUser } from "@/contexts/UsersProvider";
import { useHasChanges } from "@/hooks/useHasChanges"; import { useHasChanges } from "@/hooks/useHasChanges";
import { getOperatingSystem } from "@/hooks/useOperatingSystem"; 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 { AddExitNodeButton } from "@/modules/exit-node/AddExitNodeButton";
import { useHasExitNodes } from "@/modules/exit-node/useHasExitNodes";
import useGroupHelper from "@/modules/groups/useGroupHelper"; import useGroupHelper from "@/modules/groups/useGroupHelper";
import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton"; import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton";
import PeerRoutesTable from "@/modules/peer/PeerRoutesTable"; import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
@@ -62,8 +67,11 @@ 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 } = useFetchApi<Peer>("/peers/" + peerId); const { data: peer, isLoading } = useFetchApi<Peer>("/peers/" + peerId, true);
return peer ? (
useRedirect("/peers", false, !peerId);
return peer && !isLoading ? (
<PeerProvider peer={peer}> <PeerProvider peer={peer}>
<PeerOverview /> <PeerOverview />
</PeerProvider> </PeerProvider>
@@ -124,6 +132,9 @@ function PeerOverview() {
}); });
}; };
const { isUser } = useLoggedInUser();
const hasExitNodes = useHasExitNodes(peer);
return ( return (
<PageContainer> <PageContainer>
<RoutesProvider> <RoutesProvider>
@@ -148,29 +159,31 @@ function PeerOverview() {
/> />
<TextWithTooltip text={name} maxChars={30} /> <TextWithTooltip text={name} maxChars={30} />
<Modal {!isUser && (
open={showEditNameModal} <Modal
onOpenChange={setShowEditNameModal} open={showEditNameModal}
> onOpenChange={setShowEditNameModal}
<ModalTrigger> >
<div <ModalTrigger>
className={ <div
"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" className={
} "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"
> }
<PencilIcon size={16} /> >
</div> <PencilIcon size={16} />
</ModalTrigger> </div>
<EditNameModal </ModalTrigger>
onSuccess={(newName) => { <EditNameModal
setName(newName); onSuccess={(newName) => {
setShowEditNameModal(false); setName(newName);
}} setShowEditNameModal(false);
peer={peer} }}
initialName={name} peer={peer}
key={showEditNameModal ? 1 : 0} initialName={name}
/> key={showEditNameModal ? 1 : 0}
</Modal> />
</Modal>
)}
</h1> </h1>
<LoginExpiredBadge loginExpired={peer.login_expired} /> <LoginExpiredBadge loginExpired={peer.login_expired} />
</div> </div>
@@ -192,7 +205,7 @@ function PeerOverview() {
variant={"primary"} variant={"primary"}
className={"w-full"} className={"w-full"}
onClick={() => updatePeer()} onClick={() => updatePeer()}
disabled={!hasChanges} disabled={!hasChanges || isUser}
> >
Save Changes Save Changes
</Button> </Button>
@@ -210,18 +223,32 @@ function PeerOverview() {
"flex gap-2 items-center !text-nb-gray-300 text-xs" "flex gap-2 items-center !text-nb-gray-300 text-xs"
} }
> >
<IconInfoCircle size={14} /> {!peer.user_id ? (
<span> <>
Login expiration is disabled for all peers added with an <>
setup-key. <IconInfoCircle size={14} />
</span> <span>
Login expiration is disabled for all peers added
with an setup-key.
</span>
</>
</>
) : (
<>
<LockIcon size={14} />
<span>
{`You don't have the required permissions to update this
setting.`}
</span>
</>
)}
</div> </div>
} }
className={"w-full block"} className={"w-full block"}
disabled={!!peer.user_id} disabled={!!peer.user_id && !isUser}
> >
<FancyToggleSwitch <FancyToggleSwitch
disabled={!peer.user_id} disabled={!peer.user_id || isUser}
value={loginExpiration} value={loginExpiration}
onChange={setLoginExpiration} onChange={setLoginExpiration}
label={ label={
@@ -235,33 +262,74 @@ function PeerOverview() {
} }
/> />
</FullTooltip> </FullTooltip>
<FancyToggleSwitch <FullTooltip
value={ssh} content={
onChange={(set) => <div
!set className={
? setSsh(false) "flex gap-2 items-center !text-nb-gray-300 text-xs"
: openSSHDialog().then((confirm) => setSsh(confirm)) }
>
<LockIcon size={14} />
<span>
{`You don't have the required permissions to update this
setting.`}
</span>
</div>
} }
label={ interactive={false}
<> className={"w-full block"}
<TerminalSquare size={16} /> disabled={!isUser}
SSH Access >
</> <FancyToggleSwitch
} value={ssh}
helpText={ disabled={isUser}
"Enable the SSH server on this peer to access the machine via an secure shell." onChange={(set) =>
} !set
/> ? setSsh(false)
: openSSHDialog().then((confirm) => setSsh(confirm))
}
label={
<>
<TerminalSquare size={16} />
SSH Access
</>
}
helpText={
"Enable the SSH server on this peer to access the machine via an secure shell."
}
/>
</FullTooltip>
<div> <div>
<Label>Assigned Groups</Label> <Label>Assigned Groups</Label>
<HelpText> <HelpText>
Use groups to control what this peer can access. Use groups to control what this peer can access.
</HelpText> </HelpText>
<PeerGroupSelector <FullTooltip
onChange={setSelectedGroups} content={
values={selectedGroups} <div
peer={peer} className={
/> "flex gap-2 items-center !text-nb-gray-300 text-xs"
}
>
<LockIcon size={14} />
<span>
{`You don't have the required permissions to update this
setting.`}
</span>
</div>
}
interactive={false}
className={"w-full block"}
disabled={!isUser}
>
<PeerGroupSelector
disabled={isUser}
onChange={setSelectedGroups}
values={selectedGroups}
peer={peer}
/>
</FullTooltip>
</div> </div>
</div> </div>
</div> </div>
@@ -269,7 +337,7 @@ function PeerOverview() {
<Separator /> <Separator />
{isLinux ? ( {isLinux && !isUser ? (
<div className={"px-8 py-6"}> <div className={"px-8 py-6"}>
<div className={"max-w-6xl"}> <div className={"max-w-6xl"}>
<div className={"flex justify-between items-center"}> <div className={"flex justify-between items-center"}>
@@ -281,7 +349,8 @@ function PeerOverview() {
</Paragraph> </Paragraph>
</div> </div>
<div className={"inline-flex gap-4 justify-end"}> <div className={"inline-flex gap-4 justify-end"}>
<div> <div className={"gap-4 flex"}>
<AddExitNodeButton peer={peer} firstTime={!hasExitNodes} />
<AddRouteDropdownButton /> <AddRouteDropdownButton />
</div> </div>
</div> </div>
+8 -4
View File
@@ -17,11 +17,15 @@ import { SetupModalContent } from "@/modules/setup-netbird-modal/SetupModal";
const PeersTable = lazy(() => import("@/modules/peers/PeersTable")); const PeersTable = lazy(() => import("@/modules/peers/PeersTable"));
export default function Peers() { export default function Peers() {
const { isUser } = useLoggedInUser(); const { permission } = useLoggedInUser();
return ( return (
<PageContainer> <PageContainer>
{isUser ? <PeersDefaultView /> : <PeersView />} {permission?.dashboard_view === "blocked" ? (
<PeersBlockedView />
) : (
<PeersView />
)}
</PageContainer> </PageContainer>
); );
} }
@@ -78,11 +82,11 @@ function PeersView() {
); );
} }
function PeersDefaultView() { function PeersBlockedView() {
return ( return (
<div className={"flex items-center justify-center flex-col"}> <div className={"flex items-center justify-center flex-col"}>
<div className={"p-default py-6 max-w-3xl text-center"}> <div className={"p-default py-6 max-w-3xl text-center"}>
<h1>Add new peer to your network</h1> <h1>Add new device to your network</h1>
<Paragraph className={"inline"}> <Paragraph className={"inline"}>
To get started, install NetBird and log in using your email account. To get started, install NetBird and log in using your email account.
After that you should be connected. If you have further questions After that you should be connected. If you have further questions
+23 -3
View File
@@ -2,20 +2,35 @@
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { VerticalTabs } from "@components/VerticalTabs"; import { VerticalTabs } from "@components/VerticalTabs";
import { AlertOctagonIcon, FolderGit2Icon, ShieldIcon } from "lucide-react"; import {
import React, { useState } from "react"; AlertOctagonIcon,
FolderGit2Icon,
LockIcon,
ShieldIcon,
} from "lucide-react";
import { useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react";
import { useLoggedInUser } from "@/contexts/UsersProvider"; import { useLoggedInUser } from "@/contexts/UsersProvider";
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";
import DangerZoneTab from "@/modules/settings/DangerZoneTab"; import DangerZoneTab from "@/modules/settings/DangerZoneTab";
import GroupsTab from "@/modules/settings/GroupsTab"; import GroupsTab from "@/modules/settings/GroupsTab";
import PermissionsTab from "@/modules/settings/PermissionsTab";
export default function NetBirdSettings() { export default function NetBirdSettings() {
const [tab, setTab] = useState("authentication"); const queryParams = useSearchParams();
const queryTab = queryParams.get("tab");
const [tab, setTab] = useState(queryTab || "authentication");
const { isOwner } = useLoggedInUser(); const { isOwner } = useLoggedInUser();
const account = useAccount(); const account = useAccount();
useEffect(() => {
if (queryTab) {
setTab(queryTab);
}
}, [queryTab]);
return ( return (
<PageContainer> <PageContainer>
<VerticalTabs value={tab} onChange={setTab}> <VerticalTabs value={tab} onChange={setTab}>
@@ -28,6 +43,10 @@ export default function NetBirdSettings() {
<FolderGit2Icon size={14} /> <FolderGit2Icon size={14} />
Groups Groups
</VerticalTabs.Trigger> </VerticalTabs.Trigger>
<VerticalTabs.Trigger value="permissions">
<LockIcon size={14} />
Permissions
</VerticalTabs.Trigger>
<VerticalTabs.Trigger value="danger-zone" disabled={!isOwner}> <VerticalTabs.Trigger value="danger-zone" disabled={!isOwner}>
<AlertOctagonIcon size={14} /> <AlertOctagonIcon size={14} />
Danger zone Danger zone
@@ -36,6 +55,7 @@ export default function NetBirdSettings() {
<RestrictedAccess page={"Settings"}> <RestrictedAccess page={"Settings"}>
<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 && <GroupsTab account={account} />} {account && <GroupsTab account={account} />}
{account && <DangerZoneTab account={account} />} {account && <DangerZoneTab account={account} />}
</div> </div>
+16 -11
View File
@@ -7,7 +7,7 @@ import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess"; import { RestrictedAccess } from "@components/ui/RestrictedAccess";
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, useMemo } 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";
@@ -22,16 +22,21 @@ 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 = setupKeys?.map((setupKey) => { const setupKeysWithGroups = useMemo(() => {
if (!setupKey.auto_groups) return setupKey; if (!setupKeys) return [];
if (!groups) return setupKey; return setupKeys?.map((setupKey) => {
return { if (!setupKey.auto_groups) return setupKey;
...setupKey, if (!groups) return setupKey;
groups: setupKey.auto_groups.map((group) => { return {
return groups.find((g) => g.id === group) || undefined; ...setupKey,
}) as Group[] | undefined, groups: setupKey.auto_groups
}; ?.map((group) => {
}); return groups.find((g) => g.id === group) || undefined;
})
.filter((group) => group !== undefined) as Group[],
};
});
}, [setupKeys, groups]);
return ( return (
<PageContainer> <PageContainer>
+3
View File
@@ -10,6 +10,7 @@ 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";
@@ -42,6 +43,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 ? ( return !isLoading && user ? (
<UserOverview user={user} /> <UserOverview user={user} />
) : ( ) : (
+33 -7
View File
@@ -1,14 +1,40 @@
"use client"; "use client";
import FullScreenLoading from "@components/ui/FullScreenLoading"; import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useRouter } from "next/navigation"; import { useLocalStorage } from "@hooks/useLocalStorage";
import { useEffect } from "react"; import { useRedirect } from "@hooks/useRedirect";
import { useEffect, useState } from "react";
type Props = {
url: string;
queryParams?: string;
};
export default function NotFound() { export default function NotFound() {
const router = useRouter(); const [mounted, setMounted] = useState(false);
useEffect(() => { const [tempQueryParams, setTempQueryParams] = useLocalStorage(
router.push("/peers"); "netbird-query-params",
}); "",
);
const [queryParams, setQueryParams] = useState("");
return <FullScreenLoading />; useEffect(() => {
setQueryParams(tempQueryParams);
setTempQueryParams("");
setMounted(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return mounted ? (
<Redirect
url={window?.location?.pathname || "/"}
queryParams={queryParams}
/>
) : (
<FullScreenLoading />
);
} }
const Redirect = ({ url, queryParams }: Props) => {
useRedirect("/peers" + (queryParams && `?${queryParams}`));
return <FullScreenLoading />;
};
+34 -2
View File
@@ -1,9 +1,41 @@
"use client"; "use client";
import FullScreenLoading from "@components/ui/FullScreenLoading"; import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useLocalStorage } from "@hooks/useLocalStorage";
import { useRedirect } from "@hooks/useRedirect"; import { useRedirect } from "@hooks/useRedirect";
import { useEffect, useState } from "react";
type Props = {
url: string;
queryParams?: string;
};
export default function Home() { export default function Home() {
useRedirect("/peers"); const [mounted, setMounted] = useState(false);
return <FullScreenLoading />; const [tempQueryParams, setTempQueryParams] = useLocalStorage(
"netbird-query-params",
"",
);
const [queryParams, setQueryParams] = useState("");
useEffect(() => {
setQueryParams(tempQueryParams);
setTempQueryParams("");
setMounted(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return mounted ? (
<Redirect
url={window?.location?.pathname || "/"}
queryParams={queryParams}
/>
) : (
<FullScreenLoading />
);
} }
const Redirect = ({ url, queryParams }: Props) => {
useRedirect(url == "/" ? "/peers" : url + (queryParams && `?${queryParams}`));
return <FullScreenLoading />;
};
-20
View File
@@ -1,20 +0,0 @@
import Image from "next/image";
import * as React from "react";
import deIcon from "@/assets/countries/de.svg";
export const CountryDERounded = () => {
return (
<div
className={
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
}
>
<Image
src={deIcon}
alt={"de"}
fill={true}
className={"object-cover object-center"}
/>
</div>
);
};
-20
View File
@@ -1,20 +0,0 @@
import Image from "next/image";
import * as React from "react";
import euIcon from "@/assets/countries/eu.svg";
export const CountryEURounded = () => {
return (
<div
className={
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
}
>
<Image
src={euIcon}
alt={"eu"}
fill={true}
className={"object-cover object-center shrink-0"}
/>
</div>
);
};
-20
View File
@@ -1,20 +0,0 @@
import Image from "next/image";
import * as React from "react";
import jpIcon from "@/assets/countries/jp.svg";
export const CountryJPRounded = () => {
return (
<div
className={
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
}
>
<Image
src={jpIcon}
alt={"eu"}
fill={true}
className={"object-cover object-center"}
/>
</div>
);
};
-20
View File
@@ -1,20 +0,0 @@
import Image from "next/image";
import * as React from "react";
import usIcon from "@/assets/countries/us.svg";
export const CountryUSRounded = () => {
return (
<div
className={
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
}
>
<Image
src={usIcon}
alt={"us"}
fill={true}
className={"object-cover object-center"}
/>
</div>
);
};
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="600" viewBox="0 0 5 3">
<desc>Flag of Germany</desc>
<rect id="black_stripe" width="5" height="3" y="0" x="0" fill="#000"/>
<rect id="red_stripe" width="5" height="2" y="1" x="0" fill="#D00"/>
<rect id="gold_stripe" width="5" height="1" y="2" x="0" fill="#FFCE00"/>
</svg>

Before

Width:  |  Height:  |  Size: 493 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 810 540"><defs><g id="d"><g id="b"><path id="a" d="M0 0v1h.5z" transform="rotate(18 3.157 -.5)"/><use xlink:href="#a" transform="scale(-1 1)"/></g><g id="c"><use xlink:href="#b" transform="rotate(72)"/><use xlink:href="#b" transform="rotate(144)"/></g><use xlink:href="#c" transform="scale(-1 1)"/></g></defs><path fill="#039" d="M0 0h810v540H0z"/><g fill="#fc0" transform="matrix(30 0 0 30 405 270)"><use xlink:href="#d" y="-6"/><use xlink:href="#d" y="6"/><g id="e"><use xlink:href="#d" x="-6"/><use xlink:href="#d" transform="rotate(-144 -2.344 -2.11)"/><use xlink:href="#d" transform="rotate(144 -2.11 -2.344)"/><use xlink:href="#d" transform="rotate(72 -4.663 -2.076)"/><use xlink:href="#d" transform="rotate(72 -5.076 .534)"/></g><use xlink:href="#e" transform="scale(-1 1)"/></g></svg>

Before

Width:  |  Height:  |  Size: 888 B

-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 600">
<rect fill="#fff" height="600" width="900"/>
<circle fill="#bc002d" cx="450" cy="300" r="180"/>
</svg>

Before

Width:  |  Height:  |  Size: 166 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 7410 3900"><path fill="#b22234" d="M0 0h7410v3900H0z"/><path d="M0 450h7410m0 600H0m0 600h7410m0 600H0m0 600h7410m0 600H0" stroke="#fff" stroke-width="300"/><path fill="#3c3b6e" d="M0 0h2964v2100H0z"/><g fill="#fff"><g id="d"><g id="c"><g id="e"><g id="b"><path id="a" d="M247 90l70.534 217.082-184.66-134.164h228.253L176.466 307.082z"/><use xlink:href="#a" y="420"/><use xlink:href="#a" y="840"/><use xlink:href="#a" y="1260"/></g><use xlink:href="#a" y="1680"/></g><use xlink:href="#b" x="247" y="210"/></g><use xlink:href="#c" x="494"/></g><use xlink:href="#d" x="988"/><use xlink:href="#c" x="1976"/><use xlink:href="#e" x="2470"/></g></svg>

Before

Width:  |  Height:  |  Size: 741 B

Loaded 20 of 134 files, more files were not shown because too many files have changed in this diff. Show more