Compare commits

..
34 changed files with 190 additions and 886 deletions

No files matched your search

-3
View File
@@ -5,9 +5,6 @@ const nextConfig = {
unoptimized: true,
},
reactStrictMode: false,
env: {
APP_ENV: process.env.APP_ENV || "production",
},
};
module.exports = nextConfig;
+2 -6
View File
@@ -23,7 +23,6 @@ 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";
@@ -67,11 +66,8 @@ import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
export default function PeerPage() {
const queryParameter = useSearchParams();
const peerId = queryParameter.get("id");
const { data: peer, isLoading } = useFetchApi<Peer>("/peers/" + peerId, true);
useRedirect("/peers", false, !peerId);
return peer && !isLoading ? (
const { data: peer } = useFetchApi<Peer>("/peers/" + peerId);
return peer ? (
<PeerProvider peer={peer}>
<PeerOverview />
</PeerProvider>
-3
View File
@@ -10,7 +10,6 @@ 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";
@@ -43,8 +42,6 @@ export default function UserPage() {
return users?.find((u) => u.id === userId);
}, [users, userId]);
useRedirect("/team/users", false, !userId);
return !isLoading && user ? (
<UserOverview user={user} />
) : (
+6 -32
View File
@@ -1,40 +1,14 @@
"use client";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useLocalStorage } from "@hooks/useLocalStorage";
import { useRedirect } from "@hooks/useRedirect";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
type Props = {
url: string;
queryParams?: string;
};
export default function NotFound() {
const [mounted, setMounted] = useState(false);
const [tempQueryParams, setTempQueryParams] = useLocalStorage(
"netbird-query-params",
"",
);
const [queryParams, setQueryParams] = useState("");
const router = useRouter();
useEffect(() => {
setQueryParams(tempQueryParams);
setTempQueryParams("");
setMounted(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
router.push("/peers");
});
return mounted ? (
<Redirect
url={window?.location?.pathname || "/"}
queryParams={queryParams}
/>
) : (
<FullScreenLoading />
);
}
const Redirect = ({ url, queryParams }: Props) => {
useRedirect("/peers" + (queryParams && `?${queryParams}`));
return <FullScreenLoading />;
};
}
-2
View File
@@ -16,8 +16,6 @@ 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 -8
View File
@@ -5,16 +5,14 @@ import React, { forwardRef } from "react";
type Props = {
children: React.ReactNode;
disabled?: boolean;
className?: string;
};
function ButtonGroup({ children, disabled, className }: Props) {
function ButtonGroup({ children, disabled }: 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}
@@ -23,10 +21,7 @@ function ButtonGroup({ children, disabled, className }: Props) {
}
const ButtonGroupButton = forwardRef(
(
{ className, ...props }: ButtonProps,
ref: React.ForwardedRef<HTMLButtonElement>,
) => {
({ ...props }: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) => {
return (
<Button
ref={ref}
@@ -36,7 +31,6 @@ 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,
)}
/>
);
-1
View File
@@ -198,7 +198,6 @@ export function PeerGroupSelector({
<CommandList className={"w-full"}>
<div className={"relative"}>
<CommandInput
data-cy={"group-search-input"}
className={cn(
"min-h-[42px] w-full relative",
"border-b-0 border-t-0 border-r-0 border-l-0 border-neutral-200 dark:border-nb-gray-700 items-center",
+4 -11
View File
@@ -21,19 +21,12 @@ function SegmentedTabs({ value, onChange, children }: Props) {
);
}
function List({
children,
className = "",
}: {
children: React.ReactNode;
className?: string;
}) {
function List({ children }: { children: React.ReactNode }) {
return (
<TabsList
className={cn(
"bg-nb-gray-930/70 p-1.5 rounded-t-lg flex justify-center gap-1 border border-b-0 border-nb-gray-900",
className,
)}
className={
"bg-nb-gray-930/70 p-1.5 rounded-t-lg flex justify-center gap-1 border border-b-0 border-nb-gray-900"
}
>
{children}
</TabsList>
+1 -4
View File
@@ -75,10 +75,7 @@ const ModalContent = React.forwardRef<
<>
{children}
{showClose && (
<DialogPrimitive.Close
data-cy={"modal-close"}
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400"
>
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
@@ -28,10 +28,9 @@ export function DataTableRowsPerPage<TData>({
role="combobox"
aria-expanded={open}
disabled={disabled}
data-cy={"rows-per-page"}
className="w-[200px] justify-between"
>
<RowsIcon size={15} className={"text-nb-gray-300 shrink-0"} />
<RowsIcon size={15} className={"text-nb-gray-300"} />
<div>
<span className={"text-white"}>
{table.getState().pagination.pageSize}
@@ -48,7 +47,6 @@ export function DataTableRowsPerPage<TData>({
<CommandItem
key={val}
value={val.toString()}
data-cy={`rows-per-page-value`}
onSelect={(currentValue) => {
table.setPageSize(Number(currentValue));
setOpen(false);
-2
View File
@@ -94,7 +94,6 @@ export default function DialogProvider({ children }: Props) {
className={"w-full"}
size={"sm"}
tabIndex={-1}
data-cy={"confirmation.cancel"}
onClick={() => fn.current && fn.current(false)}
>
{dialogOptions.cancelText || "Cancel"}
@@ -110,7 +109,6 @@ export default function DialogProvider({ children }: Props) {
}
className={"w-full"}
size={"sm"}
data-cy={"confirmation.confirm"}
onClick={() => fn.current && fn.current(true)}
>
{dialogOptions.confirmText || "Confirm"}
+3 -1
View File
@@ -77,7 +77,9 @@ export default function PeerProvider({ children, peer }: Props) {
? loginExpiration
: peer.login_expiration_enabled,
approval_required:
approval_required == undefined ? undefined : approval_required,
approval_required != undefined
? approval_required
: peer.approval_required,
},
`/${peer.id}`,
);
+15 -35
View File
@@ -1,9 +1,8 @@
import loadConfig from "@utils/config";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useRef } from "react";
import { useEffect } from "react";
const config = loadConfig();
export const useRedirect = (
url: string,
replace: boolean = false,
@@ -11,43 +10,24 @@ export const useRedirect = (
) => {
const router = useRouter();
const currentPath = usePathname();
const callBackUrls = useRef([config.redirectURI, config.silentRedirectURI]);
const isRedirecting = useRef(false);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const callBackUrls = [config.redirectURI, config.silentRedirectURI];
useEffect(() => {
// If redirect is disabled or the url is already in the callback urls or the url is the current path then do not redirect
if (!enable || callBackUrls.current.includes(url) || url === currentPath)
return;
if (!enable) return;
if (callBackUrls.includes(url)) return; // Don't redirect to the callback urls to avoid infinite loop
if (url === currentPath) return; // Don't redirect to the current page
const performRedirect = () => {
if (!isRedirecting.current) {
isRedirecting.current = true;
router.refresh();
if (replace) {
router.replace(url);
} else {
router.push(url);
}
isRedirecting.current = false;
}
};
const redirect = replace ? router.replace : router.push; // Replace the current history or add a new one
performRedirect();
router.refresh();
redirect(url);
// Try to redirect after 1.25 seconds if for whatever reason the redirect did not happen (network change, browser tab open but not focused etc.)
intervalRef.current = setInterval(() => {
if (!isRedirecting.current) {
performRedirect();
}
}, 1250);
// Timer in case the user has his browser tab open but not focused
const interval = setInterval(() => {
router.refresh();
redirect(url);
}, 1000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [replace, router, url, enable, currentPath]);
return () => clearInterval(interval);
}, [replace, router, url, enable]);
};
export default useRedirect;
-12
View File
@@ -10,7 +10,6 @@ export interface PostureCheck {
os_version_check?: OperatingSystemVersionCheck;
geo_location_check?: GeoLocationCheck;
peer_network_range_check?: PeerNetworkRangeCheck;
process_check?: ProcessCheck;
};
policies?: Policy[];
active?: boolean;
@@ -54,17 +53,6 @@ export interface PeerNetworkRangeCheck {
action: "allow" | "deny";
}
export interface ProcessCheck {
processes: Process[];
}
export interface Process {
id: string;
linux_path?: string;
mac_path?: string;
windows_path?: string;
}
export const windowsKernelVersions: SelectOption[] = [
{ value: "5.0", label: "Windows 2000" },
{ value: "5.1", label: "Windows XP" },
+10 -55
View File
@@ -3,11 +3,7 @@ import { Label } from "@components/Label";
import { IconInfoCircle } from "@tabler/icons-react";
import { cn } from "@utils/helpers";
import { isLocalDev, isProduction } from "@utils/netbird";
import { isEmpty } from "lodash";
import { GlobeIcon } from "lucide-react";
import React, { useMemo } from "react";
import RoundedFlag from "@/assets/countries/RoundedFlag";
import { useCountries } from "@/contexts/CountryProvider";
import { ActivityEvent } from "@/interfaces/ActivityEvent";
type Props = {
@@ -58,8 +54,7 @@ export default function ActivityDescription({ event }: Props) {
if (event.activity_code == "setupkey.peer.add")
return (
<div className={"inline"}>
Peer <Value>{m.name}</Value> <PeerConnectionInfo meta={m} /> was added
with the NetBird IP <Value>{m.ip}</Value>
Peer <Value>{m.name}</Value> with ip <Value>{m.ip}</Value> was added
</div>
);
@@ -149,24 +144,21 @@ export default function ActivityDescription({ event }: Props) {
if (event.activity_code == "user.peer.delete")
return (
<div className={"inline"}>
Peer <Value>{m.name}</Value> <PeerConnectionInfo meta={m} /> with
NetBird IP <Value>{m.ip}</Value> was deleted
Peer <Value>{m.name}</Value> with ip <Value>{m.ip}</Value> was deleted
</div>
);
if (event.activity_code == "user.peer.add")
return (
<div className={"inline"}>
Peer <Value>{m.name}</Value> <PeerConnectionInfo meta={m} /> was added
with the NetBird IP <Value>{m.ip}</Value>
Peer <Value>{m.name}</Value> with ip <Value>{m.ip}</Value> was added
</div>
);
if (event.activity_code == "user.peer.update")
return (
<div className={"inline"}>
Peer <Value>{m.name}</Value> <PeerConnectionInfo meta={m} /> with
NetBird IP <Value>{m.ip}</Value> was updated
Peer <Value>{m.name}</Value> with ip <Value>{m.ip}</Value> was updated
</div>
);
@@ -260,15 +252,15 @@ export default function ActivityDescription({ event }: Props) {
if (event.activity_code == "peer.group.delete")
return (
<div className={"inline"}>
Group <Value>{m.group}</Value> was removed from the peer with the
NetBird IP <Value>{m.peer_ip}</Value>
Group <Value>{m.group}</Value> was removed from the peer with the ip{" "}
<Value>{m.peer_ip}</Value>
</div>
);
if (event.activity_code == "peer.group.add")
return (
<div className={"inline"}>
Group <Value>{m.group}</Value> was added to the peer with the NetBird IP{" "}
Group <Value>{m.group}</Value> was added to the peer with the ip{" "}
<Value>{m.peer_ip}</Value>
</div>
);
@@ -311,7 +303,7 @@ export default function ActivityDescription({ event }: Props) {
if (event.activity_code == "peer.rename")
return (
<div className={"inline"}>
Peer with the NetBird IP <Value>{m.ip}</Value> was renamed to{" "}
Peer with the ip <Value>{m.ip}</Value> was renamed to{" "}
<Value>{m.name}</Value>
</div>
);
@@ -319,7 +311,7 @@ export default function ActivityDescription({ event }: Props) {
if (event.activity_code == "peer.approve")
return (
<div className={"inline"}>
Peer with the NetBird IP <Value>{m.ip}</Value> was approved
Peer with the ip <Value>{m.ip}</Value> was approved
</div>
);
@@ -567,7 +559,7 @@ function Value({
return children ? (
<span
className={cn(
"text-nb-gray-200 inline-flex gap-1 items-center max-h-[22px] font-medium bg-nb-gray-900 py-[3px] text-[11px] px-[5px] border border-nb-gray-800 rounded-[4px]",
"text-nb-gray-200 inline font-medium bg-nb-gray-900 py-[3px] text-[11px] px-[5px] border border-nb-gray-800 rounded-[4px]",
className,
)}
>
@@ -575,40 +567,3 @@ function Value({
</span>
) : null;
}
function PeerConnectionInfo({ meta }: { meta: any }) {
const hasMeta =
!isEmpty(meta?.location_country_code) ||
!isEmpty(meta?.location_connection_ip);
const { countries } = useCountries();
const countryText = useMemo(() => {
if (!countries) return "Unknown";
const country = countries.find(
(c) => c.country_code === meta?.location_country_code,
);
if (!country) return "Unknown";
if (!meta?.location_city_name) return country.country_name;
return `${country.country_name}, ${meta?.location_city_name}`;
}, [countries, meta]);
return hasMeta ? (
<>
{" "}
from{" "}
{meta?.location_connection_ip && (
<Value>{meta?.location_connection_ip}</Value>
)}{" "}
{meta?.location_country_code && (
<Value>
{isEmpty(meta?.location_country_code) ? (
<GlobeIcon size={9} className={"text-nb-gray-300"} />
) : (
<RoundedFlag country={meta?.location_country_code} size={9} />
)}
{countryText}
</Value>
)}
</>
) : null;
}
+1 -12
View File
@@ -1,4 +1,3 @@
import Badge from "@components/Badge";
import Button from "@components/Button";
import {
Modal,
@@ -11,7 +10,6 @@ import ModalHeader from "@components/modal/ModalHeader";
import { PeerGroupSelector } from "@components/PeerGroupSelector";
import Separator from "@components/Separator";
import MultipleGroups from "@components/ui/MultipleGroups";
import { IconCirclePlus } from "@tabler/icons-react";
import { FolderGit2 } from "lucide-react";
import * as React from "react";
import { useMemo } from "react";
@@ -29,7 +27,6 @@ type Props = {
label?: string;
description?: string;
peer?: Peer;
showAddGroupButton?: boolean;
};
export default function GroupsRow({
@@ -40,7 +37,6 @@ export default function GroupsRow({
label = "Assigned Groups",
description = "Use groups to control what this peer can access",
peer,
showAddGroupButton = false,
}: Props) {
const { groups: allGroups } = useGroups();
const { isUser } = useLoggedInUser();
@@ -63,14 +59,7 @@ export default function GroupsRow({
setModal && !isUser && setModal(true);
}}
>
{foundGroups?.length == 0 && showAddGroupButton ? (
<Badge variant={"gray"} useHover={true}>
<IconCirclePlus size={14} />
Add Groups
</Badge>
) : (
<MultipleGroups groups={foundGroups} label={label} />
)}
<MultipleGroups groups={foundGroups} label={label} />
</ModalTrigger>
<EditGroupsModal
groups={foundGroups}
@@ -1,13 +1,11 @@
import { DropdownMenuItem } from "@components/DropdownMenu";
import { Modal } from "@components/modal/Modal";
import { getOperatingSystem } from "@hooks/useOperatingSystem";
import { IconCirclePlus, IconDirectionSign } from "@tabler/icons-react";
import { IconDirectionSign } from "@tabler/icons-react";
import * as React from "react";
import { useState } from "react";
import RoutesProvider from "@/contexts/RoutesProvider";
import { OperatingSystem } from "@/interfaces/OperatingSystem";
import { Peer } from "@/interfaces/Peer";
import { useHasExitNodes } from "@/modules/exit-node/useHasExitNodes";
import { RouteModalContent } from "@/modules/routes/RouteModal";
type Props = {
@@ -17,41 +15,24 @@ type Props = {
export const ExitNodeDropdownButton = ({ peer }: Props) => {
const [modal, setModal] = useState(false);
const isLinux = getOperatingSystem(peer.os) === OperatingSystem.LINUX;
const hasExitNodes = useHasExitNodes(peer);
return isLinux ? (
<>
<DropdownMenuItem onClick={() => setModal(true)}>
<div className={"flex gap-3 items-center w-full"}>
{hasExitNodes ? (
<>
<IconCirclePlus size={14} className={"shrink-0"} />
<div className={"flex justify-between items-center w-full"}>
Add Exit Node
</div>
</>
) : (
<>
<IconDirectionSign
size={14}
className={"shrink-0 text-yellow-400"}
/>
<div className={"flex justify-between items-center w-full"}>
Setup Exit Node
</div>
</>
)}
<IconDirectionSign size={14} className={"shrink-0"} />
<div className={"flex justify-between items-center w-full"}>
Add Exit Node
</div>
</div>
</DropdownMenuItem>
<Modal open={modal} onOpenChange={setModal}>
{modal && (
<RoutesProvider>
<RouteModalContent
onSuccess={() => setModal(false)}
peer={peer}
exitNode={true}
/>
</RoutesProvider>
<RouteModalContent
onSuccess={() => setModal(false)}
peer={peer}
exitNode={true}
/>
)}
</Modal>
</>
+2 -1
View File
@@ -13,7 +13,8 @@ export const useHasExitNodes = (peer?: Peer) => {
);
return peer
? routes?.some(
(route) => route?.peer === peer.id && route?.network === "0.0.0.0/0",
(route) =>
route?.peer === peer.id && route?.network.includes("0.0.0.0"),
) || false
: false;
};
@@ -1,310 +0,0 @@
import Button from "@components/Button";
import HelpText from "@components/HelpText";
import InlineLink from "@components/InlineLink";
import { Input } from "@components/Input";
import { Label } from "@components/Label";
import { ModalClose, ModalFooter } from "@components/modal/Modal";
import Paragraph from "@components/Paragraph";
import { cn, validator } from "@utils/helpers";
import { isEmpty, uniqueId } from "lodash";
import {
ExternalLinkIcon,
MinusCircleIcon,
PlusCircle,
ServerCogIcon,
TerminalIcon,
} from "lucide-react";
import * as React from "react";
import { useMemo, useState } from "react";
import AppleIcon from "@/assets/icons/AppleIcon";
import WindowsIcon from "@/assets/icons/WindowsIcon";
import { Process, ProcessCheck } from "@/interfaces/PostureCheck";
import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard";
type Props = {
value?: ProcessCheck;
onChange: (value: ProcessCheck | undefined) => void;
};
export const PostureCheckProcess = ({ value, onChange }: Props) => {
const [open, setOpen] = useState(false);
return (
<PostureCheckCard
open={open}
setOpen={setOpen}
key={open ? 1 : 0}
active={value?.processes && value?.processes?.length > 0}
title={"Process"}
description={
"Restrict access in your network based on running processes of a peer."
}
icon={<ServerCogIcon size={18} />}
iconClass={"bg-gradient-to-tr from-nb-gray-500 to-nb-gray-300"}
modalWidthClass={"max-w-xl"}
onReset={() => onChange(undefined)}
>
<CheckContent
value={value}
onChange={(v) => {
onChange(v);
setOpen(false);
}}
/>
</PostureCheckCard>
);
};
const CheckContent = ({ value, onChange }: Props) => {
const [processes, setProcesses] = useState<Process[]>(
value?.processes
? value.processes.map((p) => {
return {
id: uniqueId("process"),
linux_path: p?.linux_path || "",
mac_path: p?.mac_path || "",
windows_path: p?.windows_path || "",
};
})
: [
{
id: uniqueId("process"),
linux_path: "",
mac_path: "",
windows_path: "",
},
],
);
const handleProcessChange = (
id: string,
linux_path: string,
mac_path: string,
windows_path: string,
) => {
const newProcesses = processes.map((p) =>
p.id === id ? { ...p, linux_path, mac_path, windows_path } : p,
);
setProcesses(newProcesses);
};
const removeProcess = (id: string) => {
const newProcesses = processes.filter((p) => p.id !== id);
setProcesses(newProcesses);
};
const addProcess = () => {
setProcesses([
...processes,
{
id: uniqueId("process"),
linux_path: "",
mac_path: "",
windows_path: "",
},
]);
};
const pathErrors = useMemo(() => {
if (processes && processes.length > 0) {
return processes.map((p) => {
return {
id: p.id,
errorMacPath: p?.mac_path
? validator.isValidUnixFilePath(p?.mac_path || "")
? ""
: "Please enter a valid macOS file path"
: "",
errorLinuxPath: p?.linux_path
? validator.isValidUnixFilePath(p?.linux_path || "")
? ""
: "Please enter a valid Unix file path"
: "",
errorWindowsPath: p?.windows_path
? validator.isValidWindowsFilePath(p?.windows_path || "")
? ""
: "Please enter a valid Windows file path"
: "",
};
});
} else {
return [];
}
}, [processes]);
const hasErrorsOrIsEmpty = useMemo(() => {
if (processes.length === 0) return true;
const hasOnlyEmptyPaths = processes.some(
(p) => p.linux_path === "" && p.mac_path === "" && p.windows_path === "",
);
const hasPathErrors = pathErrors.some(
(e) =>
e.errorLinuxPath !== "" ||
e.errorMacPath !== "" ||
e.errorWindowsPath !== "",
);
return hasOnlyEmptyPaths || hasPathErrors;
}, [processes, pathErrors]);
return (
<>
<div className={"flex flex-col px-8 gap-2 pb-6"}>
<div className={"flex justify-between items-start gap-10 mt-2"}>
<div>
<Label>Processes</Label>
<HelpText className={""}>
Add the path of an executable file of the process. You can define
a path for Linux, macOS and Windows. Peers will only be allowed to
connect if the process is running on their system.
</HelpText>
</div>
</div>
{processes.length > 0 && (
<div className={"mb-2 flex flex-col gap-4 w-full "}>
{processes.map((p) => {
return (
<div key={p.id} className={"flex gap-2 items-center"}>
<div className={"w-full flex flex-col gap-1.5"}>
<Input
customPrefix={<TerminalIcon size={16} />}
placeholder={"/usr/local/bin/netbird"}
value={p.linux_path}
error={
pathErrors.find((e) => e.id === p.id)?.errorLinuxPath
}
errorTooltip={true}
errorTooltipPosition={"top-right"}
className={"w-full"}
onChange={(e) =>
handleProcessChange(
p.id,
e.target.value,
p?.mac_path || "",
p?.windows_path || "",
)
}
/>
<Input
customPrefix={
<AppleIcon
size={16}
className={cn(
pathErrors.find((e) => e.id === p.id)
?.errorMacPath && "fill-red-500",
)}
/>
}
placeholder={
"/Applications/NetBird.app/Contents/MacOS/netbird"
}
value={p.mac_path}
error={
pathErrors.find((e) => e.id === p.id)?.errorMacPath
}
errorTooltip={true}
errorTooltipPosition={"top-right"}
className={"w-full"}
onChange={(e) =>
handleProcessChange(
p.id,
p?.linux_path || "",
e.target.value,
p?.windows_path || "",
)
}
/>
<Input
customPrefix={
<WindowsIcon
size={16}
className={cn(
pathErrors.find((e) => e.id === p.id)
?.errorWindowsPath && "fill-red-500",
)}
/>
}
placeholder={`C:\\ProgramData\\NetBird\\netbird.exe`}
value={p.windows_path}
errorTooltip={true}
errorTooltipPosition={"top-right"}
error={
pathErrors.find((e) => e.id === p.id)?.errorWindowsPath
}
className={"w-full"}
onChange={(e) =>
handleProcessChange(
p.id,
p?.linux_path || "",
p?.mac_path || "",
e.target.value,
)
}
/>
</div>
<Button
className={"h-[42px]"}
variant={"default-outline"}
onClick={() => removeProcess(p.id)}
>
<MinusCircleIcon size={15} />
</Button>
</div>
);
})}
</div>
)}
<Button
variant={"dotted"}
size={"sm"}
onClick={addProcess}
className={"mt-1"}
>
<PlusCircle size={16} />
Add Process
</Button>
</div>
<ModalFooter className={"items-center"}>
<div className={"w-full"}>
<Paragraph className={"text-sm mt-auto"}>
Learn more about
<InlineLink
href={
"https://docs.netbird.io/how-to/manage-posture-checks#process-check"
}
target={"_blank"}
>
Process Check
<ExternalLinkIcon size={12} />
</InlineLink>
</Paragraph>
</div>
<div className={"flex gap-3 w-full justify-end"}>
<ModalClose asChild={true}>
<Button variant={"secondary"}>Cancel</Button>
</ModalClose>
<Button
variant={"primary"}
disabled={hasErrorsOrIsEmpty}
onClick={() => {
if (isEmpty(processes)) {
onChange(undefined);
} else {
onChange({
processes: processes.filter(
(p) =>
p.linux_path !== "" ||
p.mac_path !== "" ||
p.windows_path !== "",
),
});
}
}}
>
Save
</Button>
</div>
</ModalFooter>
</>
);
};
@@ -1,112 +0,0 @@
import Badge from "@components/Badge";
import FullTooltip from "@components/FullTooltip";
import { ScrollArea } from "@components/ScrollArea";
import { tryGetProcessNameFromPath } from "@utils/helpers";
import { TerminalIcon } from "lucide-react";
import * as React from "react";
import AppleIcon from "@/assets/icons/AppleIcon";
import WindowsIcon from "@/assets/icons/WindowsIcon";
import { ProcessCheck } from "@/interfaces/PostureCheck";
type Props = {
check?: ProcessCheck;
children?: React.ReactNode;
};
export const ProcessTooltip = ({ check, children }: Props) => {
return check ? (
<FullTooltip
className={"w-full min-w-0"}
interactive={true}
contentClassName={"p-0"}
content={
<div
className={
"text-neutral-300 text-sm max-w-xs flex flex-col gap-1 min-w-0"
}
>
<div className={"px-4 pt-3"}>
<span>
<span className={"text-green-500 font-semibold"}>Allow only</span>{" "}
peers which are running the following processes
</span>
</div>
<ScrollArea
className={
"max-h-[275px] overflow-y-auto flex flex-col px-4 min-w-0"
}
>
<div className={"flex flex-col gap-3 mt-1 text-xs mb-3.5 min-w-0"}>
{check.processes.map((p, index) => {
return (
<div className={"flex-col flex gap-1 min-w-0"} key={index}>
{p?.linux_path && (
<Badge
variant={"gray"}
useHover={false}
className={"justify-start font-medium text-xs min-w-0"}
>
<span className={"mr-1.5"}>
<TerminalIcon size={12} />
</span>
<span
className={"truncate inline-block "}
title={p?.linux_path}
>
{tryGetProcessNameFromPath(p?.linux_path) ||
"Unknown path"}
</span>
</Badge>
)}
{p?.mac_path && (
<Badge
variant={"gray"}
useHover={false}
className={"justify-start font-medium text-xs min-w-0"}
>
<span className={"mr-1.5"}>
<AppleIcon size={12} />
</span>
<span
className={"truncate inline-block "}
title={p?.mac_path}
>
{tryGetProcessNameFromPath(p?.mac_path) ||
"Unknown path"}
</span>
</Badge>
)}
{p?.windows_path && (
<Badge
variant={"gray"}
useHover={false}
className={"justify-start font-medium text-xs min-w-0"}
>
<span className={"mr-1.5"}>
<WindowsIcon size={12} />
</span>
<span
className={"truncate inline-block"}
title={p?.windows_path}
>
{tryGetProcessNameFromPath(p?.windows_path) ||
"Unknown path"}
</span>
</Badge>
)}
</div>
);
})}
</div>
</ScrollArea>
</div>
}
>
{children}
</FullTooltip>
) : (
children
);
};
Loaded 20 of 34 files, more files were not shown because too many files have changed in this diff. Show more