mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e553f3c83 | ||
|
|
79164e9dd5 | ||
|
|
5caeab118b | ||
|
|
3f943bb7d4 | ||
|
|
96b939e6cc | ||
|
|
5e13548b81 | ||
|
|
2272a1d2a4 | ||
|
|
fc3da50346 | ||
|
|
6d4716cdad | ||
|
|
859916b1df | ||
|
|
80ce7d21b0 | ||
|
|
06fdbd8ec4 | ||
|
|
973cceff79 | ||
|
|
f4a2d6fae8 | ||
|
|
cb922b46b7 | ||
|
|
4c56ae704c |
No files matched your search
+4
-1
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 />;
|
||||||
|
};
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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 +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 |
@@ -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 +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
Reference in new issue
Block a user