mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e553f3c83 | ||
|
|
79164e9dd5 | ||
|
|
5caeab118b | ||
|
|
3f943bb7d4 | ||
|
|
96b939e6cc | ||
|
|
5e13548b81 | ||
|
|
2272a1d2a4 | ||
|
|
fc3da50346 |
No files matched your search
@@ -5,6 +5,9 @@ const nextConfig = {
|
||||
unoptimized: true,
|
||||
},
|
||||
reactStrictMode: false,
|
||||
env: {
|
||||
APP_ENV: process.env.APP_ENV || "production",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -23,6 +23,7 @@ import Separator from "@components/Separator";
|
||||
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
||||
import LoginExpiredBadge from "@components/ui/LoginExpiredBadge";
|
||||
import TextWithTooltip from "@components/ui/TextWithTooltip";
|
||||
import useRedirect from "@hooks/useRedirect";
|
||||
import { IconCloudLock, IconInfoCircle } from "@tabler/icons-react";
|
||||
import useFetchApi from "@utils/api";
|
||||
import dayjs from "dayjs";
|
||||
@@ -57,6 +58,8 @@ import { getOperatingSystem } from "@/hooks/useOperatingSystem";
|
||||
import { OperatingSystem } from "@/interfaces/OperatingSystem";
|
||||
import type { Peer } from "@/interfaces/Peer";
|
||||
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 AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton";
|
||||
import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
|
||||
@@ -64,8 +67,11 @@ import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
|
||||
export default function PeerPage() {
|
||||
const queryParameter = useSearchParams();
|
||||
const peerId = queryParameter.get("id");
|
||||
const { data: peer } = useFetchApi<Peer>("/peers/" + peerId);
|
||||
return peer ? (
|
||||
const { data: peer, isLoading } = useFetchApi<Peer>("/peers/" + peerId, true);
|
||||
|
||||
useRedirect("/peers", false, !peerId);
|
||||
|
||||
return peer && !isLoading ? (
|
||||
<PeerProvider peer={peer}>
|
||||
<PeerOverview />
|
||||
</PeerProvider>
|
||||
@@ -127,6 +133,7 @@ function PeerOverview() {
|
||||
};
|
||||
|
||||
const { isUser } = useLoggedInUser();
|
||||
const hasExitNodes = useHasExitNodes(peer);
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
@@ -342,7 +349,8 @@ function PeerOverview() {
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"inline-flex gap-4 justify-end"}>
|
||||
<div>
|
||||
<div className={"gap-4 flex"}>
|
||||
<AddExitNodeButton peer={peer} firstTime={!hasExitNodes} />
|
||||
<AddRouteDropdownButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import SkeletonTable from "@components/skeletons/SkeletonTable";
|
||||
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
|
||||
import useFetchApi from "@utils/api";
|
||||
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 { useGroups } from "@/contexts/GroupsProvider";
|
||||
import { Group } from "@/interfaces/Group";
|
||||
@@ -22,16 +22,21 @@ export default function SetupKeys() {
|
||||
const { data: setupKeys, isLoading } = useFetchApi<SetupKey[]>("/setup-keys");
|
||||
const { groups } = useGroups();
|
||||
|
||||
const setupKeysWithGroups = setupKeys?.map((setupKey) => {
|
||||
if (!setupKey.auto_groups) return setupKey;
|
||||
if (!groups) return setupKey;
|
||||
return {
|
||||
...setupKey,
|
||||
groups: setupKey.auto_groups.map((group) => {
|
||||
return groups.find((g) => g.id === group) || undefined;
|
||||
}) as Group[] | undefined,
|
||||
};
|
||||
});
|
||||
const setupKeysWithGroups = useMemo(() => {
|
||||
if (!setupKeys) return [];
|
||||
return setupKeys?.map((setupKey) => {
|
||||
if (!setupKey.auto_groups) return setupKey;
|
||||
if (!groups) return setupKey;
|
||||
return {
|
||||
...setupKey,
|
||||
groups: setupKey.auto_groups
|
||||
?.map((group) => {
|
||||
return groups.find((g) => g.id === group) || undefined;
|
||||
})
|
||||
.filter((group) => group !== undefined) as Group[],
|
||||
};
|
||||
});
|
||||
}, [setupKeys, groups]);
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
|
||||
@@ -10,6 +10,7 @@ import Paragraph from "@components/Paragraph";
|
||||
import { PeerGroupSelector } from "@components/PeerGroupSelector";
|
||||
import Separator from "@components/Separator";
|
||||
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
||||
import useRedirect from "@hooks/useRedirect";
|
||||
import { IconCirclePlus, IconSettings2 } from "@tabler/icons-react";
|
||||
import useFetchApi, { useApiCall } from "@utils/api";
|
||||
import { generateColorFromString } from "@utils/helpers";
|
||||
@@ -42,6 +43,8 @@ export default function UserPage() {
|
||||
return users?.find((u) => u.id === userId);
|
||||
}, [users, userId]);
|
||||
|
||||
useRedirect("/team/users", false, !userId);
|
||||
|
||||
return !isLoading && user ? (
|
||||
<UserOverview user={user} />
|
||||
) : (
|
||||
|
||||
+33
-7
@@ -1,14 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { useLocalStorage } from "@hooks/useLocalStorage";
|
||||
import { useRedirect } from "@hooks/useRedirect";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type Props = {
|
||||
url: string;
|
||||
queryParams?: string;
|
||||
};
|
||||
export default function NotFound() {
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
router.push("/peers");
|
||||
});
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [tempQueryParams, setTempQueryParams] = useLocalStorage(
|
||||
"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";
|
||||
|
||||
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
||||
import { useLocalStorage } from "@hooks/useLocalStorage";
|
||||
import { useRedirect } from "@hooks/useRedirect";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type Props = {
|
||||
url: string;
|
||||
queryParams?: string;
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
useRedirect("/peers");
|
||||
return <FullScreenLoading />;
|
||||
const [mounted, setMounted] = useState(false);
|
||||
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 |
Binary file not shown.
@@ -16,6 +16,8 @@ export default function CircleIcon({
|
||||
return (
|
||||
<span
|
||||
style={{ width: size + "px", height: size + "px" }}
|
||||
data-cy="circle-icon"
|
||||
data-cy-status={active ? "active" : "inactive"}
|
||||
className={cn(
|
||||
"rounded-full",
|
||||
active
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useOidc, useOidcUser } from "@axa-fr/react-oidc";
|
||||
import Button from "@components/Button";
|
||||
import Paragraph from "@components/Paragraph";
|
||||
import loadConfig from "@utils/config";
|
||||
import { ArrowRightIcon, LogOut } from "lucide-react";
|
||||
import { ArrowRightIcon } from "lucide-react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -55,7 +55,7 @@ export const OIDCError = () => {
|
||||
variant={"primary"}
|
||||
size={"sm"}
|
||||
className={"mt-5"}
|
||||
onClick={() => login("/", { client_id: config.clientId })}
|
||||
onClick={() => logout("/", { client_id: config.clientId })}
|
||||
>
|
||||
Continue
|
||||
<ArrowRightIcon size={16} />
|
||||
@@ -83,7 +83,6 @@ export const OIDCError = () => {
|
||||
onClick={() => logout("/", { client_id: config.clientId })}
|
||||
>
|
||||
Logout
|
||||
<LogOut size={16} />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
OidcConfiguration,
|
||||
} from "@axa-fr/react-oidc/dist/vanilla/oidc";
|
||||
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
||||
import { useLocalStorage } from "@hooks/useLocalStorage";
|
||||
import { useRedirect } from "@hooks/useRedirect";
|
||||
import loadConfig, { buildExtras } from "@utils/config";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
@@ -43,6 +44,19 @@ export default function OIDCProvider({ children }: Props) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const router = useRouter();
|
||||
const path = usePathname();
|
||||
const params = useSearchParams()?.toString();
|
||||
const [, setQueryParams] = useLocalStorage("netbird-query-params", params);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
params?.includes("tab") ||
|
||||
params?.includes("search") ||
|
||||
params?.includes("id")
|
||||
) {
|
||||
setQueryParams(params);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const withCustomHistory = () => {
|
||||
return {
|
||||
|
||||
@@ -5,14 +5,16 @@ import React, { forwardRef } from "react";
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function ButtonGroup({ children, disabled }: Props) {
|
||||
function ButtonGroup({ children, disabled, className }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-lg border-[1px] dark:border-nb-gray-900 border-neutral-200 overflow-hidden flex items-center justify-center shrink-0 border-separate",
|
||||
disabled ? "opacity-100 !border-nb-gray-900/20" : "",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -21,7 +23,10 @@ function ButtonGroup({ children, disabled }: Props) {
|
||||
}
|
||||
|
||||
const ButtonGroupButton = forwardRef(
|
||||
({ ...props }: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) => {
|
||||
(
|
||||
{ className, ...props }: ButtonProps,
|
||||
ref: React.ForwardedRef<HTMLButtonElement>,
|
||||
) => {
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
@@ -31,6 +36,7 @@ const ButtonGroupButton = forwardRef(
|
||||
className={cn(
|
||||
"first:border-l-0 last:border-r-0 border-t-0 border-b-0 h-[41px]",
|
||||
"!py-2.5 !px-4",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -12,14 +12,14 @@ export default function HelpText({
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<p
|
||||
<span
|
||||
className={cn(
|
||||
"text-[.8rem] dark:text-nb-gray-300",
|
||||
"text-[.8rem] dark:text-nb-gray-300 block font-light tracking-wide",
|
||||
margin && "mb-2",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Loaded 20 of 79 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user