fix(i18n): localize Peer sections and Posture Checks components
- AccessiblePeersSection, PeerNetworkRoutesSection, PeerRemoteJobsSection - PostureCheckGeoLocation, PostureCheckNetBirdVersion, PostureCheckOperatingSystem - PostureCheckPeerNetworkRange, PostureCheckProcess, PostureCheckNoChecksInfo - Update en.ts and zh.ts messages with new translation keys
This commit is contained in:
@@ -322,7 +322,10 @@ saveGroups: "Save Groups",
|
||||
getStartedDescription: "It looks like you don't have any connected machines.\nGet started by adding one to your network.",
|
||||
learnMoreInOur: "Learn more in our",
|
||||
gettingStartedGuide: "Getting Started Guide",
|
||||
userPeersDescription: "View all peers registered by this user."
|
||||
userPeersDescription: "View all peers registered by this user.",
|
||||
accessiblePeersDesc: "This peer can connect to the following peers within the NetBird network.",
|
||||
networkRoutesDesc: "Access other networks without installing NetBird on every resource.",
|
||||
remoteJobsDesc: "Remotely trigger actions such as debug bundles or other tasks on this peer, without requiring CLI access."
|
||||
},
|
||||
policies: {
|
||||
title: "Policies",
|
||||
@@ -980,7 +983,56 @@ serviceUsersDescription: "Use service users to create API tokens and avoid losin
|
||||
postureCheckNameHelp: "Set an easily identifiable name for your posture check.",
|
||||
postureCheckNamePlaceholder: "e.g., NetBird Version > 0.25.0",
|
||||
postureCheckDescriptionHelp: "Write a short description to add more context to this policy.",
|
||||
postureCheckDescriptionPlaceholder: "e.g., Check if the NetBird version is bigger than 0.25.0"
|
||||
postureCheckDescriptionPlaceholder: "e.g., Check if the NetBird version is bigger than 0.25.0",
|
||||
netBirdClientVersion: "NetBird Client Version",
|
||||
netBirdClientVersionHelp: "Restrict access to peers with a specific NetBird client version.",
|
||||
netBirdClientVersionCheck: "Client Version Check",
|
||||
minimumRequiredVersion: "Minimum required version",
|
||||
minimumRequiredVersionHelp: "Only peers with the minimum specified NetBird client version will have access to the network.",
|
||||
minimumRequiredVersionPlaceholder: "e.g., 0.25.0",
|
||||
minimumRequiredVersionError: "Please enter a valid version, e.g., 0.2, 0.2.0, 0.2.0-alpha.1",
|
||||
countryAndRegion: "Country & Region",
|
||||
countryAndRegionHelp: "Restrict access in your network based on country or region.",
|
||||
countryAndRegionCheck: "Country & Region Check",
|
||||
geoLite2License: "This check includes GeoLite2 data created by MaxMind, available from",
|
||||
allowOrBlockLocation: "Allow or Block Location",
|
||||
chooseAllowOrBlock: "Choose whether you want to allow or block access from specific countries or regions",
|
||||
addLocation: "Add Location",
|
||||
learnMoreAbout: "Learn more about",
|
||||
operatingSystemHelp: "Restrict access in your network based on the operating system.",
|
||||
operatingSystemCheck: "Operating System Check",
|
||||
allVersions: "All versions",
|
||||
equalOrGreaterThan: "Equal or greater than",
|
||||
allowOrBlock: "Allow or Block",
|
||||
allowOrBlockOSHelp: "Choose whether you want to allow or block the operating system.",
|
||||
selectVersion: "Select version...",
|
||||
versionPlaceholder: "e.g., 6.0.0",
|
||||
useCustomVersion: "Use custom version number",
|
||||
useCustomVersionHelp: "Use a custom version number if you need more control.",
|
||||
kernelVersion: "Kernel Version",
|
||||
process: "Process",
|
||||
processHelp: "Restrict access in your network based on running processes of a peer.",
|
||||
processCheck: "Process Check",
|
||||
processes: "Processes",
|
||||
processesHelp: "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.",
|
||||
addProcess: "Add Process",
|
||||
linuxPathPlaceholder: "/usr/local/bin/netbird",
|
||||
macPathPlaceholder: "/Applications/NetBird.app/Contents/MacOS/netbird",
|
||||
windowsPathPlaceholder: "C:\\ProgramData\\NetBird\\netbird.exe",
|
||||
validMacPath: "Please enter a valid macOS file path",
|
||||
validUnixPath: "Please enter a valid Unix file path",
|
||||
validWindowsPath: "Please enter a valid Windows file path",
|
||||
peerNetworkRangeHelp: "Restrict access by allowing or blocking peer network ranges.",
|
||||
peerNetworkRangeCheck: "Peer Network Range Check",
|
||||
allowOrBlockRanges: "Allow or Block Ranges",
|
||||
allowOrBlockRangesHelp: "Choose whether you want to allow or block specific peer network ranges",
|
||||
addNetworkRange: "Add Network Range",
|
||||
validCidr: "Please enter a valid CIDR, e.g., 192.168.1.0/24",
|
||||
cidrPlaceholder: "e.g., 172.16.0.0/16",
|
||||
noChecks: "You haven't added any posture checks yet",
|
||||
noChecksDescription: "Add various posture checks to further restrict access in your network. E.g., only clients with a specific NetBird client version, operating system or location are allowed to connect.",
|
||||
browseChecks: "Browse Checks",
|
||||
newPostureCheck: "New Posture Check"
|
||||
},
|
||||
setupKeys: {
|
||||
title: "Setup Keys",
|
||||
|
||||
@@ -322,7 +322,10 @@ saveGroups: "保存组",
|
||||
getStartedDescription: "看起来您还没有任何连接的设备。\n开始使用,向您的网络中添加一台设备。",
|
||||
learnMoreInOur: "在我们的",
|
||||
gettingStartedGuide: "入门指南",
|
||||
userPeersDescription: "查看此用户注册的所有节点。"
|
||||
userPeersDescription: "查看此用户注册的所有节点。",
|
||||
accessiblePeersDesc: "此节点可以连接到 NetBird 网络中的以下节点。",
|
||||
networkRoutesDesc: "无需在每个资源上安装 NetBird 即可访问其他网络。",
|
||||
remoteJobsDesc: "远程触发此节点上的操作,如调试包或其他任务,无需 CLI 访问。"
|
||||
},
|
||||
policies: {
|
||||
title: "策略",
|
||||
@@ -980,7 +983,56 @@ disable2FA: "禁用两步验证",
|
||||
postureCheckNameHelp: "为姿态检查设置一个易于识别的名称。",
|
||||
postureCheckNamePlaceholder: "例如:NetBird 版本 > 0.25.0",
|
||||
postureCheckDescriptionHelp: "写一个简短的描述为此策略添加更多上下文。",
|
||||
postureCheckDescriptionPlaceholder: "例如:检查 NetBird 版本是否大于 0.25.0"
|
||||
postureCheckDescriptionPlaceholder: "例如:检查 NetBird 版本是否大于 0.25.0",
|
||||
netBirdClientVersion: "NetBird 客户端版本",
|
||||
netBirdClientVersionHelp: "根据特定的 NetBird 客户端版本限制对节点的访问。",
|
||||
netBirdClientVersionCheck: "客户端版本检查",
|
||||
minimumRequiredVersion: "最低所需版本",
|
||||
minimumRequiredVersionHelp: "仅具有指定最低 NetBird 客户端版本的对等节点才能访问网络。",
|
||||
minimumRequiredVersionPlaceholder: "例如:0.25.0",
|
||||
minimumRequiredVersionError: "请输入有效版本,例如:0.2, 0.2.0, 0.2.0-alpha.1",
|
||||
countryAndRegion: "国家与地区",
|
||||
countryAndRegionHelp: "根据国家或地区限制网络中的访问。",
|
||||
countryAndRegionCheck: "国家与地区检查",
|
||||
geoLite2License: "此检查包含由 MaxMind 创建的 GeoLite2 数据,来源于",
|
||||
allowOrBlockLocation: "允许或阻止位置",
|
||||
chooseAllowOrBlock: "选择您希望允许或阻止来自特定国家或地区的访问",
|
||||
addLocation: "添加位置",
|
||||
learnMoreAbout: "了解更多关于",
|
||||
operatingSystemHelp: "根据操作系统限制网络中的访问。",
|
||||
operatingSystemCheck: "操作系统检查",
|
||||
allVersions: "所有版本",
|
||||
equalOrGreaterThan: "等于或大于",
|
||||
allowOrBlock: "允许或阻止",
|
||||
allowOrBlockOSHelp: "选择您希望允许或阻止的操作系统。",
|
||||
selectVersion: "选择版本...",
|
||||
versionPlaceholder: "例如:6.0.0",
|
||||
useCustomVersion: "使用自定义版本号",
|
||||
useCustomVersionHelp: "如果需要更多控制,请使用自定义版本号。",
|
||||
kernelVersion: "内核版本",
|
||||
process: "进程",
|
||||
processHelp: "根据对等节点上正在运行的进程限制网络中的访问。",
|
||||
processCheck: "进程检查",
|
||||
processes: "进程",
|
||||
processesHelp: "添加进程的可执行文件路径。您可以为 Linux、macOS 和 Windows 定义路径。仅当进程在系统上运行时,对等节点才允许连接。",
|
||||
addProcess: "添加进程",
|
||||
linuxPathPlaceholder: "/usr/local/bin/netbird",
|
||||
macPathPlaceholder: "/Applications/NetBird.app/Contents/MacOS/netbird",
|
||||
windowsPathPlaceholder: "C:\\ProgramData\\NetBird\\netbird.exe",
|
||||
validMacPath: "请输入有效的 macOS 文件路径",
|
||||
validUnixPath: "请输入有效的 Unix 文件路径",
|
||||
validWindowsPath: "请输入有效的 Windows 文件路径",
|
||||
peerNetworkRangeHelp: "通过允许或阻止对等节点网络范围来限制访问。",
|
||||
peerNetworkRangeCheck: "对等节点网络范围检查",
|
||||
allowOrBlockRanges: "允许或阻止范围",
|
||||
allowOrBlockRangesHelp: "选择您希望允许或阻止特定对等节点网络范围",
|
||||
addNetworkRange: "添加网络范围",
|
||||
validCidr: "请输入有效的 CIDR,例如:192.168.1.0/24",
|
||||
cidrPlaceholder: "例如:172.16.0.0/16",
|
||||
noChecks: "您还没有添加任何态势检查",
|
||||
noChecksDescription: "添加各种态势检查以进一步限制网络中的访问。例如,仅允许具有特定 NetBird 客户端版本、操作系统或位置的客户端连接。",
|
||||
browseChecks: "浏览检查",
|
||||
newPostureCheck: "新建态势检查"
|
||||
},
|
||||
setupKeys: {
|
||||
title: "安装密钥",
|
||||
|
||||
@@ -1,72 +1,74 @@
|
||||
import InlineLink from "@components/InlineLink";
|
||||
import Paragraph from "@components/Paragraph";
|
||||
import SkeletonTable, {
|
||||
SkeletonTableHeader,
|
||||
SkeletonTableHeader,
|
||||
} from "@components/skeletons/SkeletonTable";
|
||||
import useFetchApi from "@utils/api";
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useUsers } from "@/contexts/UsersProvider";
|
||||
import type { Peer } from "@/interfaces/Peer";
|
||||
|
||||
const AccessiblePeersTable = lazy(
|
||||
() => import("@/modules/peer/MinimalPeersTable"),
|
||||
() => import("@/modules/peer/MinimalPeersTable"),
|
||||
);
|
||||
|
||||
type Props = {
|
||||
peerID: string;
|
||||
peerID: string;
|
||||
};
|
||||
export const AccessiblePeersSection = ({ peerID }: Props) => {
|
||||
const { data: peers, isLoading } = useFetchApi<Peer[]>(
|
||||
`/peers/${peerID}/accessible-peers`,
|
||||
);
|
||||
const { users } = useUsers();
|
||||
const t = useTranslations("peers");
|
||||
const tCommon = useTranslations("common");
|
||||
const { data: peers, isLoading } = useFetchApi<Peer[]>(
|
||||
`/peers/${peerID}/accessible-peers`,
|
||||
);
|
||||
const { users } = useUsers();
|
||||
|
||||
const peersWithUser = peers?.map((peer) => {
|
||||
if (!users) return peer;
|
||||
return {
|
||||
...peer,
|
||||
user: users?.find((user) => user.id === peer.user_id),
|
||||
};
|
||||
});
|
||||
const peersWithUser = peers?.map((peer) => {
|
||||
if (!users) return peer;
|
||||
return {
|
||||
...peer,
|
||||
user: users?.find((user) => user.id === peer.user_id),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={"pb-10 px-8"}>
|
||||
<div className={""}>
|
||||
<div className={"flex justify-between items-center mb-5"}>
|
||||
<div>
|
||||
<Paragraph>
|
||||
This peer can connect to the following peers within the NetBird
|
||||
network.{" "}
|
||||
<InlineLink
|
||||
href={"https://docs.netbird.io/how-to/manage-network-access"}
|
||||
target={"_blank"}
|
||||
>
|
||||
Learn more
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className={"pb-10 px-8"}>
|
||||
<div className={""}>
|
||||
<div className={"flex justify-between items-center mb-5"}>
|
||||
<div>
|
||||
<Paragraph>
|
||||
{t("accessiblePeersDesc")}{" "}
|
||||
<InlineLink
|
||||
href={"https://docs.netbird.io/how-to/manage-network-access"}
|
||||
target={"_blank"}
|
||||
>
|
||||
{tCommon("learnMore")}
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<SkeletonTableHeader className={"!p-0"} />
|
||||
<div className={"mt-8 w-full"}>
|
||||
<SkeletonTable withHeader={false} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<AccessiblePeersTable
|
||||
peerID={peerID}
|
||||
isLoading={isLoading}
|
||||
peers={peersWithUser}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<SkeletonTableHeader className={"!p-0"} />
|
||||
<div className={"mt-8 w-full"}>
|
||||
<SkeletonTable withHeader={false} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<AccessiblePeersTable
|
||||
peerID={peerID}
|
||||
isLoading={isLoading}
|
||||
peers={peersWithUser}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,63 +9,65 @@ import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton";
|
||||
import usePeerRoutes from "@/modules/peer/usePeerRoutes";
|
||||
import InlineLink from "@components/InlineLink";
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const PeerRoutesTable = lazy(() => import("@/modules/peer/PeerRoutesTable"));
|
||||
|
||||
type Props = {
|
||||
peer: Peer;
|
||||
peer: Peer;
|
||||
};
|
||||
|
||||
export const PeerNetworkRoutesSection = ({ peer }: Props) => {
|
||||
const { peerRoutes, isLoading } = usePeerRoutes({ peer });
|
||||
const exitNodeInfo = useHasExitNodes(peer);
|
||||
const t = useTranslations("peers");
|
||||
const tCommon = useTranslations("common");
|
||||
const { peerRoutes, isLoading } = usePeerRoutes({ peer });
|
||||
const exitNodeInfo = useHasExitNodes(peer);
|
||||
|
||||
return (
|
||||
<div className={"pb-10 px-8"}>
|
||||
<div className={""}>
|
||||
<div className={"flex justify-between items-center mb-5"}>
|
||||
<div>
|
||||
<Paragraph>
|
||||
Access other networks without installing NetBird on every
|
||||
resource.{" "}
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/how-to/routing-traffic-to-private-networks"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
Learn more
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"inline-flex gap-4 justify-end"}>
|
||||
<div className={"gap-4 flex"}>
|
||||
<AddExitNodeButton
|
||||
peer={peer}
|
||||
firstTime={!exitNodeInfo.hasExitNode}
|
||||
/>
|
||||
<AddRouteDropdownButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className={"pb-10 px-8"}>
|
||||
<div className={""}>
|
||||
<div className={"flex justify-between items-center mb-5"}>
|
||||
<div>
|
||||
<Paragraph>
|
||||
{t("networkRoutesDesc")}{" "}
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/how-to/routing-traffic-to-private-networks"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
{tCommon("learnMore")}
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"inline-flex gap-4 justify-end"}>
|
||||
<div className={"gap-4 flex"}>
|
||||
<AddExitNodeButton
|
||||
peer={peer}
|
||||
firstTime={!exitNodeInfo.hasExitNode}
|
||||
/>
|
||||
<AddRouteDropdownButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<div className={"mt-0 w-full"}>
|
||||
<SkeletonTable withHeader={false} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<PeerRoutesTable
|
||||
peer={peer}
|
||||
isLoading={isLoading}
|
||||
peerRoutes={peerRoutes}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<div className={"mt-0 w-full"}>
|
||||
<SkeletonTable withHeader={false} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<PeerRoutesTable
|
||||
peer={peer}
|
||||
isLoading={isLoading}
|
||||
peerRoutes={peerRoutes}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,59 +1,61 @@
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import React, { lazy, Suspense } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import InlineLink from "@/components/InlineLink";
|
||||
import Paragraph from "@/components/Paragraph";
|
||||
import SkeletonTable, {
|
||||
SkeletonTableHeader,
|
||||
SkeletonTableHeader,
|
||||
} from "@/components/skeletons/SkeletonTable";
|
||||
import { Job } from "@/interfaces/Job";
|
||||
import useFetchApi from "@/utils/api";
|
||||
|
||||
const PeerRemoteJobsTable = lazy(
|
||||
() => import("@/modules/jobs/table/PeerRemoteJobsTable"),
|
||||
() => import("@/modules/jobs/table/PeerRemoteJobsTable"),
|
||||
);
|
||||
type Props = {
|
||||
peerID: string;
|
||||
peerID: string;
|
||||
};
|
||||
|
||||
export const PeerRemoteJobsSection = ({ peerID }: Props) => {
|
||||
const { data: jobs, isLoading } = useFetchApi<Job[]>(`/peers/${peerID}/jobs`);
|
||||
const t = useTranslations("peers");
|
||||
const tCommon = useTranslations("common");
|
||||
const { data: jobs, isLoading } = useFetchApi<Job[]>(`/peers/${peerID}/jobs`);
|
||||
|
||||
return (
|
||||
<div className="pb-10 px-8">
|
||||
<div className="">
|
||||
<div className="flex justify-between items-center mb-5">
|
||||
<div>
|
||||
<Paragraph>
|
||||
Remotely trigger actions such as debug bundles or other tasks on
|
||||
this peer, without requiring CLI access.{" "}
|
||||
<InlineLink
|
||||
href={"https://docs.netbird.io/manage/peers/remote-jobs"}
|
||||
target={"_blank"}
|
||||
>
|
||||
Learn more
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="pb-10 px-8">
|
||||
<div className="">
|
||||
<div className="flex justify-between items-center mb-5">
|
||||
<div>
|
||||
<Paragraph>
|
||||
{t("remoteJobsDesc")}{" "}
|
||||
<InlineLink
|
||||
href={"https://docs.netbird.io/manage/peers/remote-jobs"}
|
||||
target={"_blank"}
|
||||
>
|
||||
{tCommon("learnMore")}
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<SkeletonTableHeader className="!p-0" />
|
||||
<div className="mt-8 w-full">
|
||||
<SkeletonTable withHeader={false} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<PeerRemoteJobsTable
|
||||
peerID={peerID}
|
||||
jobs={jobs}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<SkeletonTableHeader className="!p-0" />
|
||||
<div className="mt-8 w-full">
|
||||
<SkeletonTable withHeader={false} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<PeerRemoteJobsTable
|
||||
peerID={peerID}
|
||||
jobs={jobs}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,12 +9,12 @@ import { CitySelector } from "@components/ui/CitySelector";
|
||||
import { CountrySelector } from "@components/ui/CountrySelector";
|
||||
import { isEmpty, uniqueId } from "lodash";
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
FlagIcon,
|
||||
MinusCircleIcon,
|
||||
PlusCircle,
|
||||
ShieldCheck,
|
||||
ShieldXIcon,
|
||||
ExternalLinkIcon,
|
||||
FlagIcon,
|
||||
MinusCircleIcon,
|
||||
PlusCircle,
|
||||
ShieldCheck,
|
||||
ShieldXIcon,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
@@ -23,195 +23,192 @@ import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type Props = {
|
||||
value?: GeoLocationCheck;
|
||||
onChange: (value: GeoLocationCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
value?: GeoLocationCheck;
|
||||
onChange: (value: GeoLocationCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const PostureCheckGeoLocation = ({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const t = useTranslations("postureChecks");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
icon={<FlagIcon size={16} />}
|
||||
title={"Country & Region"}
|
||||
description={
|
||||
"Restrict access in your network based on country or region."
|
||||
}
|
||||
iconClass={"bg-gradient-to-tr from-indigo-500 to-indigo-400"}
|
||||
modalWidthClass={"max-w-2xl"}
|
||||
active={value ? value?.locations?.length > 0 : false}
|
||||
onReset={() => onChange(undefined)}
|
||||
license={
|
||||
<div className={"text-xs max-w-xs"}>
|
||||
This check includes GeoLite2 data created by MaxMind, available from{" "}
|
||||
<InlineLink href={"https://www.maxmind.com"} target={"_blank"}>
|
||||
https://www.maxmind.com
|
||||
</InlineLink>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
icon={<FlagIcon size={16} />}
|
||||
title={t("countryAndRegion")}
|
||||
description={t("countryAndRegionHelp")}
|
||||
iconClass={"bg-gradient-to-tr from-indigo-500 to-indigo-400"}
|
||||
modalWidthClass={"max-w-2xl"}
|
||||
active={value ? value?.locations?.length > 0 : false}
|
||||
onReset={() => onChange(undefined)}
|
||||
license={
|
||||
<div className={"text-xs max-w-xs"}>
|
||||
{t("geoLite2License")}{" "}
|
||||
<InlineLink href={"https://www.maxmind.com"} target={"_blank"}>
|
||||
https://www.maxmind.com
|
||||
</InlineLink>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckContent = ({ value, onChange, disabled }: Props) => {
|
||||
const t = useTranslations("common");
|
||||
const [allowDenyLocation, setAllowDenyLocation] = useState<string>(
|
||||
value?.action ? value.action : "allow",
|
||||
);
|
||||
const [locations, setLocations] = useState<GeoLocation[]>(
|
||||
value?.locations.map((l) => {
|
||||
return {
|
||||
id: uniqueId("location"),
|
||||
country_code: l.country_code,
|
||||
city_name: l.city_name || "",
|
||||
};
|
||||
}) || [],
|
||||
);
|
||||
const t = useTranslations("postureChecks");
|
||||
const tCommon = useTranslations("common");
|
||||
const [allowDenyLocation, setAllowDenyLocation] = useState<string>(
|
||||
value?.action ? value.action : "allow",
|
||||
);
|
||||
const [locations, setLocations] = useState<GeoLocation[]>(
|
||||
value?.locations.map((l) => {
|
||||
return {
|
||||
id: uniqueId("location"),
|
||||
country_code: l.country_code,
|
||||
city_name: l.city_name || "",
|
||||
};
|
||||
}) || [],
|
||||
);
|
||||
|
||||
const updateLocation = (id: string, location: GeoLocation) => {
|
||||
const find = locations.find((l) => l.id === id);
|
||||
if (find) {
|
||||
Object.assign(find, location);
|
||||
setLocations([...locations]);
|
||||
}
|
||||
};
|
||||
const updateLocation = (id: string, location: GeoLocation) => {
|
||||
const find = locations.find((l) => l.id === id);
|
||||
if (find) {
|
||||
Object.assign(find, location);
|
||||
setLocations([...locations]);
|
||||
}
|
||||
};
|
||||
|
||||
const removeLocation = (id: string) => {
|
||||
setLocations(locations.filter((l) => l.id !== id));
|
||||
};
|
||||
const removeLocation = (id: string) => {
|
||||
setLocations(locations.filter((l) => l.id !== id));
|
||||
};
|
||||
|
||||
const addLocation = () => {
|
||||
setLocations([
|
||||
...locations,
|
||||
{ id: uniqueId("location"), country_code: "AF", city_name: "" },
|
||||
]);
|
||||
};
|
||||
const addLocation = () => {
|
||||
setLocations([
|
||||
...locations,
|
||||
{ id: uniqueId("location"), country_code: "AF", city_name: "" },
|
||||
]);
|
||||
};
|
||||
|
||||
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>Allow or Block Location</Label>
|
||||
<HelpText className={""}>
|
||||
Choose whether you want to allow or block access from specific
|
||||
countries or regions
|
||||
</HelpText>
|
||||
</div>
|
||||
<RadioGroup value={allowDenyLocation} onChange={setAllowDenyLocation}>
|
||||
<RadioGroupItem value={"allow"} variant={"green"}>
|
||||
<ShieldCheck size={16} />
|
||||
Allow
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={"deny"} variant={"red"}>
|
||||
<ShieldXIcon size={16} />
|
||||
Block
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{locations.length > 0 && (
|
||||
<div className={"mb-2 flex flex-col gap-2 w-full "}>
|
||||
{locations.map((location) => {
|
||||
return (
|
||||
<div key={location.id} className={"flex gap-2"}>
|
||||
<CountrySelector
|
||||
value={location.country_code}
|
||||
onChange={(value) => {
|
||||
updateLocation(location.id, {
|
||||
...location,
|
||||
country_code: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{location.country_code && (
|
||||
<CitySelector
|
||||
value={location.city_name || ""}
|
||||
onChange={(value) => {
|
||||
updateLocation(location.id, {
|
||||
...location,
|
||||
city_name: value,
|
||||
});
|
||||
}}
|
||||
country={location.country_code}
|
||||
/>
|
||||
)}
|
||||
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>{t("allowOrBlockLocation")}</Label>
|
||||
<HelpText className={""}>{t("chooseAllowOrBlock")}</HelpText>
|
||||
</div>
|
||||
<RadioGroup value={allowDenyLocation} onChange={setAllowDenyLocation}>
|
||||
<RadioGroupItem value={"allow"} variant={"green"}>
|
||||
<ShieldCheck size={16} />
|
||||
{tCommon("allow")}
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={"deny"} variant={"red"}>
|
||||
<ShieldXIcon size={16} />
|
||||
{tCommon("block")}
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{locations.length > 0 && (
|
||||
<div className={"mb-2 flex flex-col gap-2 w-full "}>
|
||||
{locations.map((location) => {
|
||||
return (
|
||||
<div key={location.id} className={"flex gap-2"}>
|
||||
<CountrySelector
|
||||
value={location.country_code}
|
||||
onChange={(value) => {
|
||||
updateLocation(location.id, {
|
||||
...location,
|
||||
country_code: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{location.country_code && (
|
||||
<CitySelector
|
||||
value={location.city_name || ""}
|
||||
onChange={(value) => {
|
||||
updateLocation(location.id, {
|
||||
...location,
|
||||
city_name: value,
|
||||
});
|
||||
}}
|
||||
country={location.country_code}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className={"h-[42px]"}
|
||||
variant={"default-outline"}
|
||||
onClick={() => removeLocation(location.id)}
|
||||
>
|
||||
<MinusCircleIcon size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant={"dotted"}
|
||||
size={"sm"}
|
||||
disabled={allowDenyLocation == "all" || disabled}
|
||||
onClick={addLocation}
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
Add Location
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
className={"h-[42px]"}
|
||||
variant={"default-outline"}
|
||||
onClick={() => removeLocation(location.id)}
|
||||
>
|
||||
<MinusCircleIcon size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant={"dotted"}
|
||||
size={"sm"}
|
||||
disabled={allowDenyLocation == "all" || disabled}
|
||||
onClick={addLocation}
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
{t("addLocation")}
|
||||
</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#geolocation-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
Country & Region Check
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{t("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
onClick={() => {
|
||||
if (isEmpty(locations)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({
|
||||
action: allowDenyLocation as "allow" | "deny",
|
||||
locations: locations,
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
<ModalFooter className={"items-center"}>
|
||||
<div className={"w-full"}>
|
||||
<Paragraph className={"text-sm mt-auto"}>
|
||||
{t("learnMoreAbout")}
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/how-to/manage-posture-checks#geolocation-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
{t("countryAndRegionCheck")}
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
onClick={() => {
|
||||
if (isEmpty(locations)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({
|
||||
action: allowDenyLocation as "allow" | "deny",
|
||||
locations: locations,
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
{tCommon("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,120 +16,116 @@ import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type Props = {
|
||||
value?: NetBirdVersionCheck;
|
||||
onChange: (value: NetBirdVersionCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
value?: NetBirdVersionCheck;
|
||||
onChange: (value: NetBirdVersionCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const PostureCheckNetBirdVersion = ({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const t = useTranslations("postureChecks");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
key={open ? 1 : 0}
|
||||
active={value?.min_version !== undefined}
|
||||
title={"NetBird Client Version"}
|
||||
description={
|
||||
"Restrict access to peers with a specific NetBird client version."
|
||||
}
|
||||
icon={<NetBirdIcon size={18} />}
|
||||
modalWidthClass={"max-w-lg"}
|
||||
onReset={() => onChange(undefined)}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
key={open ? 1 : 0}
|
||||
active={value?.min_version !== undefined}
|
||||
title={t("netBirdClientVersion")}
|
||||
description={t("netBirdClientVersionHelp")}
|
||||
icon={<NetBirdIcon size={18} />}
|
||||
modalWidthClass={"max-w-lg"}
|
||||
onReset={() => onChange(undefined)}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckContent = ({ value, onChange, disabled }: Props) => {
|
||||
const t = useTranslations("common");
|
||||
const [version, setVersion] = useState(value?.min_version || "");
|
||||
const t = useTranslations("postureChecks");
|
||||
const tCommon = useTranslations("common");
|
||||
const [version, setVersion] = useState(value?.min_version || "");
|
||||
|
||||
const versionError = useMemo(() => {
|
||||
if (version == "") return "";
|
||||
const validSemver = validator.isValidVersion(version);
|
||||
if (!validSemver)
|
||||
return "Please enter a valid version, e.g., 0.2, 0.2.0, 0.2.0-alpha.1";
|
||||
}, [version]);
|
||||
const versionError = useMemo(() => {
|
||||
if (version == "") return "";
|
||||
const validSemver = validator.isValidVersion(version);
|
||||
if (!validSemver) return t("minimumRequiredVersionError");
|
||||
}, [version]);
|
||||
|
||||
const canSave = useMemo(() => {
|
||||
return (
|
||||
!versionError &&
|
||||
version !== value?.min_version &&
|
||||
!isEmpty(version) &&
|
||||
!disabled
|
||||
);
|
||||
}, [version, versionError, value, disabled]);
|
||||
const canSave = useMemo(() => {
|
||||
return (
|
||||
!versionError &&
|
||||
version !== value?.min_version &&
|
||||
!isEmpty(version) &&
|
||||
!disabled
|
||||
);
|
||||
}, [version, versionError, value, disabled]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={"flex flex-col px-8 gap-3 pb-6"}>
|
||||
<div>
|
||||
<Label>Minimum required version</Label>
|
||||
<HelpText>
|
||||
Only peers with the minimum specified NetBird client version will
|
||||
have access to the network.
|
||||
</HelpText>
|
||||
<div>
|
||||
<Input
|
||||
className={"max-w-[200px]"}
|
||||
value={version}
|
||||
onChange={(e) => setVersion(e.target.value)}
|
||||
placeholder={"e.g., 0.25.0"}
|
||||
error={versionError}
|
||||
customPrefix={"Version"}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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#net-bird-client-version-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
Client Version Check
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{t("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
disabled={!canSave}
|
||||
onClick={() => {
|
||||
if (isEmpty(version)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({ min_version: version });
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className={"flex flex-col px-8 gap-3 pb-6"}>
|
||||
<div>
|
||||
<Label>{t("minimumRequiredVersion")}</Label>
|
||||
<HelpText>{t("minimumRequiredVersionHelp")}</HelpText>
|
||||
<div>
|
||||
<Input
|
||||
className={"max-w-[200px]"}
|
||||
value={version}
|
||||
onChange={(e) => setVersion(e.target.value)}
|
||||
placeholder={t("minimumRequiredVersionPlaceholder")}
|
||||
error={versionError}
|
||||
customPrefix={t("version")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ModalFooter className={"items-center"}>
|
||||
<div className={"w-full"}>
|
||||
<Paragraph className={"text-sm mt-auto"}>
|
||||
{t("learnMoreAbout")}
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/how-to/manage-posture-checks#net-bird-client-version-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
{t("netBirdClientVersionCheck")}
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
disabled={!canSave}
|
||||
onClick={() => {
|
||||
if (isEmpty(version)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({ min_version: version });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{tCommon("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,20 +8,20 @@ import { ModalClose, ModalFooter } from "@components/modal/Modal";
|
||||
import Paragraph from "@components/Paragraph";
|
||||
import { RadioGroup, RadioGroupItem } from "@components/RadioGroup";
|
||||
import {
|
||||
SelectDropdown,
|
||||
SelectOption,
|
||||
SelectDropdown,
|
||||
SelectOption,
|
||||
} from "@components/select/SelectDropdown";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@components/Tabs";
|
||||
import { IconMathEqualGreater } from "@tabler/icons-react";
|
||||
import { validator } from "@utils/helpers";
|
||||
import { isEmpty } from "lodash";
|
||||
import {
|
||||
Disc3Icon,
|
||||
ExternalLinkIcon,
|
||||
FileCog,
|
||||
GalleryHorizontalEnd,
|
||||
ShieldCheck,
|
||||
ShieldXIcon,
|
||||
Disc3Icon,
|
||||
ExternalLinkIcon,
|
||||
FileCog,
|
||||
GalleryHorizontalEnd,
|
||||
ShieldCheck,
|
||||
ShieldXIcon,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
@@ -32,410 +32,409 @@ import { LinuxIcon } from "@/assets/icons/LinuxIcon";
|
||||
import WindowsIcon from "@/assets/icons/WindowsIcon";
|
||||
import { OperatingSystem } from "@/interfaces/OperatingSystem";
|
||||
import {
|
||||
androidVersions,
|
||||
iOSVersions,
|
||||
macOSVersions,
|
||||
OperatingSystemVersionCheck,
|
||||
windowsKernelVersions,
|
||||
androidVersions,
|
||||
iOSVersions,
|
||||
macOSVersions,
|
||||
OperatingSystemVersionCheck,
|
||||
windowsKernelVersions,
|
||||
} from "@/interfaces/PostureCheck";
|
||||
import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type Props = {
|
||||
value?: OperatingSystemVersionCheck;
|
||||
onChange: (value: OperatingSystemVersionCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
value?: OperatingSystemVersionCheck;
|
||||
onChange: (value: OperatingSystemVersionCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const PostureCheckOperatingSystem = ({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const t = useTranslations("postureChecks");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
key={open ? 1 : 0}
|
||||
icon={<Disc3Icon size={16} />}
|
||||
title={"Operating System"}
|
||||
modalWidthClass={"max-w-xl"}
|
||||
description={
|
||||
"Restrict access in your network based on the operating system."
|
||||
}
|
||||
iconClass={"bg-gradient-to-tr from-nb-gray-500 to-nb-gray-300"}
|
||||
active={value !== undefined}
|
||||
onReset={() => onChange(undefined)}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
key={open ? 1 : 0}
|
||||
icon={<Disc3Icon size={16} />}
|
||||
title={t("operatingSystem")}
|
||||
modalWidthClass={"max-w-xl"}
|
||||
description={t("operatingSystemHelp")}
|
||||
iconClass={"bg-gradient-to-tr from-nb-gray-500 to-nb-gray-300"}
|
||||
active={value !== undefined}
|
||||
onReset={() => onChange(undefined)}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckContent = ({ value, onChange, disabled }: Props) => {
|
||||
const t = useTranslations("common");
|
||||
const [tab] = useState(String(OperatingSystem.LINUX));
|
||||
const t = useTranslations("postureChecks");
|
||||
const tCommon = useTranslations("common");
|
||||
const [tab] = useState(String(OperatingSystem.LINUX));
|
||||
|
||||
const firstTimeCheck = value === undefined;
|
||||
const firstTimeCheck = value === undefined;
|
||||
|
||||
const [windowsVersion, setWindowsVersion] = useState<string>(
|
||||
firstTimeCheck
|
||||
? ""
|
||||
: value && value.windows
|
||||
? value.windows.min_kernel_version
|
||||
: "-",
|
||||
);
|
||||
const [macOSVersion, setMacOSVersion] = useState<string>(
|
||||
firstTimeCheck
|
||||
? ""
|
||||
: value && value.darwin
|
||||
? value.darwin?.min_version
|
||||
: "-",
|
||||
);
|
||||
const [androidVersion, setAndroidVersion] = useState<string>(
|
||||
firstTimeCheck
|
||||
? ""
|
||||
: value && value.android
|
||||
? value.android?.min_version
|
||||
: "-",
|
||||
);
|
||||
const [iOSVersion, setIOSVersion] = useState<string>(
|
||||
firstTimeCheck ? "" : value && value.ios ? value.ios?.min_version : "-",
|
||||
);
|
||||
const [linuxVersion, setLinuxVersion] = useState<string>(
|
||||
firstTimeCheck
|
||||
? ""
|
||||
: value && value.linux
|
||||
? value.linux?.min_kernel_version
|
||||
: "-",
|
||||
);
|
||||
const [windowsVersion, setWindowsVersion] = useState<string>(
|
||||
firstTimeCheck
|
||||
? ""
|
||||
: value && value.windows
|
||||
? value.windows.min_kernel_version
|
||||
: "-",
|
||||
);
|
||||
const [macOSVersion, setMacOSVersion] = useState<string>(
|
||||
firstTimeCheck
|
||||
? ""
|
||||
: value && value.darwin
|
||||
? value.darwin?.min_version
|
||||
: "-",
|
||||
);
|
||||
const [androidVersion, setAndroidVersion] = useState<string>(
|
||||
firstTimeCheck
|
||||
? ""
|
||||
: value && value.android
|
||||
? value.android?.min_version
|
||||
: "-",
|
||||
);
|
||||
const [iOSVersion, setIOSVersion] = useState<string>(
|
||||
firstTimeCheck ? "" : value && value.ios ? value.ios?.min_version : "-",
|
||||
);
|
||||
const [linuxVersion, setLinuxVersion] = useState<string>(
|
||||
firstTimeCheck
|
||||
? ""
|
||||
: value && value.linux
|
||||
? value.linux?.min_kernel_version
|
||||
: "-",
|
||||
);
|
||||
|
||||
const [linuxError, setLinuxError] = useState("");
|
||||
const [windowsError, setWindowsError] = useState("");
|
||||
const [macOSError, setMacOSError] = useState("");
|
||||
const [iOSError, setIOSError] = useState("");
|
||||
const [androidError, setAndroidError] = useState("");
|
||||
const [linuxError, setLinuxError] = useState("");
|
||||
const [windowsError, setWindowsError] = useState("");
|
||||
const [macOSError, setMacOSError] = useState("");
|
||||
const [iOSError, setIOSError] = useState("");
|
||||
const [androidError, setAndroidError] = useState("");
|
||||
|
||||
const versionError =
|
||||
linuxError ||
|
||||
windowsError ||
|
||||
macOSError ||
|
||||
iOSError ||
|
||||
androidError ||
|
||||
disabled;
|
||||
const versionError =
|
||||
linuxError ||
|
||||
windowsError ||
|
||||
macOSError ||
|
||||
iOSError ||
|
||||
androidError ||
|
||||
disabled;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs defaultValue={tab}>
|
||||
<TabsList justify={"start"} className={"px-8"}>
|
||||
<TabsTrigger value={String(OperatingSystem.LINUX)}>
|
||||
<LinuxIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
Linux
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={String(OperatingSystem.WINDOWS)}>
|
||||
<WindowsIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
Windows
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={String(OperatingSystem.APPLE)}>
|
||||
<AppleIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
macOS
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={String(OperatingSystem.IOS)}>
|
||||
<IOSIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
iOS
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={String(OperatingSystem.ANDROID)}>
|
||||
<AndroidIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
Android
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value={String(OperatingSystem.LINUX)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
value={linuxVersion}
|
||||
onChange={setLinuxVersion}
|
||||
os={OperatingSystem.LINUX}
|
||||
onError={setLinuxError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value={String(OperatingSystem.WINDOWS)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
versionList={windowsKernelVersions}
|
||||
value={windowsVersion}
|
||||
onChange={setWindowsVersion}
|
||||
os={OperatingSystem.WINDOWS}
|
||||
onError={setWindowsError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value={String(OperatingSystem.APPLE)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
versionList={macOSVersions}
|
||||
value={macOSVersion}
|
||||
onChange={setMacOSVersion}
|
||||
os={OperatingSystem.APPLE}
|
||||
onError={setMacOSError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value={String(OperatingSystem.IOS)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
versionList={iOSVersions}
|
||||
value={iOSVersion}
|
||||
onChange={setIOSVersion}
|
||||
os={OperatingSystem.IOS}
|
||||
onError={setIOSError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value={String(OperatingSystem.ANDROID)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
versionList={androidVersions}
|
||||
value={androidVersion}
|
||||
onChange={setAndroidVersion}
|
||||
os={OperatingSystem.ANDROID}
|
||||
onError={setAndroidError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<div className={"h-6"}></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#operating-system-version-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
Operating System Check
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{t("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
disabled={!!versionError}
|
||||
variant={"primary"}
|
||||
onClick={() => {
|
||||
const osCheck = {} as OperatingSystemVersionCheck;
|
||||
return (
|
||||
<>
|
||||
<Tabs defaultValue={tab}>
|
||||
<TabsList justify={"start"} className={"px-8"}>
|
||||
<TabsTrigger value={String(OperatingSystem.LINUX)}>
|
||||
<LinuxIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
Linux
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={String(OperatingSystem.WINDOWS)}>
|
||||
<WindowsIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
Windows
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={String(OperatingSystem.APPLE)}>
|
||||
<AppleIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
macOS
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={String(OperatingSystem.IOS)}>
|
||||
<IOSIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
iOS
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={String(OperatingSystem.ANDROID)}>
|
||||
<AndroidIcon
|
||||
className={
|
||||
"fill-nb-gray-500 group-data-[state=active]/trigger:fill-netbird transition-all"
|
||||
}
|
||||
/>
|
||||
Android
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value={String(OperatingSystem.LINUX)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
value={linuxVersion}
|
||||
onChange={setLinuxVersion}
|
||||
os={OperatingSystem.LINUX}
|
||||
onError={setLinuxError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value={String(OperatingSystem.WINDOWS)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
versionList={windowsKernelVersions}
|
||||
value={windowsVersion}
|
||||
onChange={setWindowsVersion}
|
||||
os={OperatingSystem.WINDOWS}
|
||||
onError={setWindowsError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value={String(OperatingSystem.APPLE)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
versionList={macOSVersions}
|
||||
value={macOSVersion}
|
||||
onChange={setMacOSVersion}
|
||||
os={OperatingSystem.APPLE}
|
||||
onError={setMacOSError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value={String(OperatingSystem.IOS)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
versionList={iOSVersions}
|
||||
value={iOSVersion}
|
||||
onChange={setIOSVersion}
|
||||
os={OperatingSystem.IOS}
|
||||
onError={setIOSError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value={String(OperatingSystem.ANDROID)} className={"px-8"}>
|
||||
<OperatingSystemTab
|
||||
versionList={androidVersions}
|
||||
value={androidVersion}
|
||||
onChange={setAndroidVersion}
|
||||
os={OperatingSystem.ANDROID}
|
||||
onError={setAndroidError}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<div className={"h-6"}></div>
|
||||
<ModalFooter className={"items-center"}>
|
||||
<div className={"w-full"}>
|
||||
<Paragraph className={"text-sm mt-auto"}>
|
||||
{t("learnMoreAbout")}
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/how-to/manage-posture-checks#operating-system-version-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
{t("operatingSystemCheck")}
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
disabled={!!versionError}
|
||||
variant={"primary"}
|
||||
onClick={() => {
|
||||
const osCheck = {} as OperatingSystemVersionCheck;
|
||||
|
||||
if (windowsVersion !== "-") {
|
||||
osCheck.windows = { min_kernel_version: windowsVersion };
|
||||
}
|
||||
if (macOSVersion !== "-") {
|
||||
osCheck.darwin = { min_version: macOSVersion };
|
||||
}
|
||||
if (androidVersion !== "-") {
|
||||
osCheck.android = { min_version: androidVersion };
|
||||
}
|
||||
if (iOSVersion !== "-") {
|
||||
osCheck.ios = { min_version: iOSVersion };
|
||||
}
|
||||
if (linuxVersion !== "-") {
|
||||
osCheck.linux = { min_kernel_version: linuxVersion };
|
||||
}
|
||||
if (windowsVersion !== "-") {
|
||||
osCheck.windows = { min_kernel_version: windowsVersion };
|
||||
}
|
||||
if (macOSVersion !== "-") {
|
||||
osCheck.darwin = { min_version: macOSVersion };
|
||||
}
|
||||
if (androidVersion !== "-") {
|
||||
osCheck.android = { min_version: androidVersion };
|
||||
}
|
||||
if (iOSVersion !== "-") {
|
||||
osCheck.ios = { min_version: iOSVersion };
|
||||
}
|
||||
if (linuxVersion !== "-") {
|
||||
osCheck.linux = { min_kernel_version: linuxVersion };
|
||||
}
|
||||
|
||||
if (isEmpty(osCheck)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange(osCheck);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
if (isEmpty(osCheck)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange(osCheck);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{tCommon("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type OperatingSystemTabProps = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
versionList?: SelectOption[];
|
||||
os: OperatingSystem;
|
||||
onError: (error: string) => void;
|
||||
disabled?: boolean;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
versionList?: SelectOption[];
|
||||
os: OperatingSystem;
|
||||
onError: (error: string) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const allOrMinOptions = [
|
||||
{
|
||||
label: "All versions",
|
||||
value: "all",
|
||||
icon: GalleryHorizontalEnd,
|
||||
},
|
||||
{
|
||||
label: "Equal or greater than",
|
||||
value: "min",
|
||||
icon: IconMathEqualGreater,
|
||||
},
|
||||
] as SelectOption[];
|
||||
|
||||
export const OperatingSystemTab = ({
|
||||
value,
|
||||
onChange,
|
||||
versionList,
|
||||
os,
|
||||
onError,
|
||||
disabled,
|
||||
value,
|
||||
onChange,
|
||||
versionList,
|
||||
os,
|
||||
onError,
|
||||
disabled,
|
||||
}: OperatingSystemTabProps) => {
|
||||
const [allow, setAllow] = useState(value == "-" ? "block" : "allow");
|
||||
const [allOrMin, setAllOrMin] = useState(
|
||||
value == "" || value == "-" || value == "0" ? "all" : "min",
|
||||
);
|
||||
const [useCustomVersion, setUseCustomVersion] = useState(() => {
|
||||
if (!versionList) return false;
|
||||
if (!value) return false;
|
||||
if (value === "-") return false;
|
||||
if (value === "0") return false;
|
||||
const find = versionList.map((v) => v.value).includes(value);
|
||||
return !find;
|
||||
});
|
||||
const t = useTranslations("postureChecks");
|
||||
const tCommon = useTranslations("common");
|
||||
const allOrMinOptions: SelectOption[] = [
|
||||
{
|
||||
label: t("allVersions"),
|
||||
value: "all",
|
||||
icon: GalleryHorizontalEnd,
|
||||
},
|
||||
{
|
||||
label: t("equalOrGreaterThan"),
|
||||
value: "min",
|
||||
icon: IconMathEqualGreater,
|
||||
},
|
||||
];
|
||||
const [allow, setAllow] = useState(value == "-" ? "block" : "allow");
|
||||
const [allOrMin, setAllOrMin] = useState(
|
||||
value == "" || value == "-" || value == "0" ? "all" : "min",
|
||||
);
|
||||
const [useCustomVersion, setUseCustomVersion] = useState(() => {
|
||||
if (!versionList) return false;
|
||||
if (!value) return false;
|
||||
if (value === "-") return false;
|
||||
if (value === "0") return false;
|
||||
const find = versionList.map((v) => v.value).includes(value);
|
||||
return !find;
|
||||
});
|
||||
|
||||
const changeAllow = (value: string) => {
|
||||
setAllow(value);
|
||||
if (value === "block") {
|
||||
setAllOrMin("all");
|
||||
onChange("-");
|
||||
setAllOrMin("all");
|
||||
setUseCustomVersion(false);
|
||||
} else {
|
||||
onChange("");
|
||||
setAllOrMin("all");
|
||||
setUseCustomVersion(false);
|
||||
}
|
||||
};
|
||||
const changeAllow = (value: string) => {
|
||||
setAllow(value);
|
||||
if (value === "block") {
|
||||
setAllOrMin("all");
|
||||
onChange("-");
|
||||
setAllOrMin("all");
|
||||
setUseCustomVersion(false);
|
||||
} else {
|
||||
onChange("");
|
||||
setAllOrMin("all");
|
||||
setUseCustomVersion(false);
|
||||
}
|
||||
};
|
||||
|
||||
const changeAllOrMin = (option: string) => {
|
||||
setAllOrMin(option);
|
||||
if (option === "all") {
|
||||
onChange("");
|
||||
} else if (option === "min" && value == "" && versionList) {
|
||||
const getLast = versionList[versionList.length - 1];
|
||||
onChange(getLast.value);
|
||||
}
|
||||
};
|
||||
const changeAllOrMin = (option: string) => {
|
||||
setAllOrMin(option);
|
||||
if (option === "all") {
|
||||
onChange("");
|
||||
} else if (option === "min" && value == "" && versionList) {
|
||||
const getLast = versionList[versionList.length - 1];
|
||||
onChange(getLast.value);
|
||||
}
|
||||
};
|
||||
|
||||
const prefix =
|
||||
os === OperatingSystem.LINUX || os === OperatingSystem.WINDOWS
|
||||
? "Kernel Version"
|
||||
: "Version";
|
||||
const prefix =
|
||||
os === OperatingSystem.LINUX || os === OperatingSystem.WINDOWS
|
||||
? t("kernelVersion")
|
||||
: tCommon("version");
|
||||
|
||||
const versionError = useMemo(() => {
|
||||
const msg = "Please enter a valid version, e.g., 0.2, 0.2.0, 0.2.0-alpha.1";
|
||||
if (value == "") return "";
|
||||
if (value == "-") return "";
|
||||
const validSemver = validator.isValidVersion(value);
|
||||
if (!validSemver) return msg;
|
||||
return "";
|
||||
}, [value]);
|
||||
const versionError = useMemo(() => {
|
||||
const msg = t("minimumRequiredVersionError");
|
||||
if (value == "") return "";
|
||||
if (value == "-") return "";
|
||||
const validSemver = validator.isValidVersion(value);
|
||||
if (!validSemver) return msg;
|
||||
return "";
|
||||
}, [value, t]);
|
||||
|
||||
useEffect(() => {
|
||||
onError(versionError);
|
||||
}, [versionError, onError]);
|
||||
useEffect(() => {
|
||||
onError(versionError);
|
||||
}, [versionError, onError]);
|
||||
|
||||
return (
|
||||
<div className={""}>
|
||||
<div className={"flex justify-between items-start gap-10 "}>
|
||||
<div>
|
||||
<Label>Allow or Block</Label>
|
||||
<HelpText>
|
||||
Choose whether you want to allow or block the operating system.
|
||||
</HelpText>
|
||||
</div>
|
||||
<RadioGroup value={allow} onChange={changeAllow}>
|
||||
<RadioGroupItem value={"allow"} variant={"green"}>
|
||||
<ShieldCheck size={14} />
|
||||
Allow
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={"block"} variant={"red"}>
|
||||
<ShieldXIcon size={14} />
|
||||
Block
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className={"gap-4 items-center grid grid-cols-2 mt-3"}>
|
||||
<SelectDropdown
|
||||
value={allOrMin}
|
||||
onChange={changeAllOrMin}
|
||||
options={allOrMinOptions}
|
||||
disabled={allow === "block" || disabled}
|
||||
/>
|
||||
{versionList && !useCustomVersion ? (
|
||||
<SelectDropdown
|
||||
value={value || "0"}
|
||||
showSearch={true}
|
||||
placeholder={"Select version..."}
|
||||
onChange={onChange}
|
||||
options={versionList}
|
||||
disabled={allOrMin === "all" || allow === "block" || disabled}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
value={value}
|
||||
customPrefix={prefix}
|
||||
placeholder={"e.g., 6.0.0"}
|
||||
error={versionError}
|
||||
errorTooltip={true}
|
||||
disabled={allOrMin === "all" || allow === "block" || disabled}
|
||||
onChange={(v) => {
|
||||
onChange(v.target.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{os !== OperatingSystem.LINUX && (
|
||||
<div className={"mt-4"}>
|
||||
<FancyToggleSwitch
|
||||
disabled={allow === "block" || allOrMin === "all" || disabled}
|
||||
value={useCustomVersion}
|
||||
onChange={setUseCustomVersion}
|
||||
label={
|
||||
<>
|
||||
<FileCog size={14} />
|
||||
Use custom version number
|
||||
</>
|
||||
}
|
||||
helpText={"Use a custom version number if you need more control."}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={""}>
|
||||
<div className={"flex justify-between items-start gap-10 "}>
|
||||
<div>
|
||||
<Label>{t("allowOrBlock")}</Label>
|
||||
<HelpText>{t("allowOrBlockOSHelp")}</HelpText>
|
||||
</div>
|
||||
<RadioGroup value={allow} onChange={changeAllow}>
|
||||
<RadioGroupItem value={"allow"} variant={"green"}>
|
||||
<ShieldCheck size={14} />
|
||||
{tCommon("allow")}
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={"block"} variant={"red"}>
|
||||
<ShieldXIcon size={14} />
|
||||
{tCommon("block")}
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className={"gap-4 items-center grid grid-cols-2 mt-3"}>
|
||||
<SelectDropdown
|
||||
value={allOrMin}
|
||||
onChange={changeAllOrMin}
|
||||
options={allOrMinOptions}
|
||||
disabled={allow === "block" || disabled}
|
||||
/>
|
||||
{versionList && !useCustomVersion ? (
|
||||
<SelectDropdown
|
||||
value={value || "0"}
|
||||
showSearch={true}
|
||||
placeholder={t("selectVersion")}
|
||||
onChange={onChange}
|
||||
options={versionList}
|
||||
disabled={allOrMin === "all" || allow === "block" || disabled}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
value={value}
|
||||
customPrefix={prefix}
|
||||
placeholder={t("versionPlaceholder")}
|
||||
error={versionError}
|
||||
errorTooltip={true}
|
||||
disabled={allOrMin === "all" || allow === "block" || disabled}
|
||||
onChange={(v) => {
|
||||
onChange(v.target.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{os !== OperatingSystem.LINUX && (
|
||||
<div className={"mt-4"}>
|
||||
<FancyToggleSwitch
|
||||
disabled={allow === "block" || allOrMin === "all" || disabled}
|
||||
value={useCustomVersion}
|
||||
onChange={setUseCustomVersion}
|
||||
label={
|
||||
<>
|
||||
<FileCog size={14} />
|
||||
{t("useCustomVersion")}
|
||||
</>
|
||||
}
|
||||
helpText={t("useCustomVersionHelp")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,12 +9,12 @@ import { RadioGroup, RadioGroupItem } from "@components/RadioGroup";
|
||||
import cidr from "ip-cidr";
|
||||
import { isEmpty, uniqueId } from "lodash";
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
MinusCircleIcon,
|
||||
NetworkIcon,
|
||||
PlusCircle,
|
||||
ShieldCheck,
|
||||
ShieldXIcon,
|
||||
ExternalLinkIcon,
|
||||
MinusCircleIcon,
|
||||
NetworkIcon,
|
||||
PlusCircle,
|
||||
ShieldCheck,
|
||||
ShieldXIcon,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
@@ -23,212 +23,209 @@ import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type Props = {
|
||||
value?: PeerNetworkRangeCheck;
|
||||
onChange: (value: PeerNetworkRangeCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
value?: PeerNetworkRangeCheck;
|
||||
onChange: (value: PeerNetworkRangeCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const PostureCheckPeerNetworkRange = ({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const t = useTranslations("postureChecks");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
key={open ? 1 : 0}
|
||||
icon={<NetworkIcon size={16} />}
|
||||
title={"Peer Network Range"}
|
||||
modalWidthClass={"max-w-xl"}
|
||||
description={
|
||||
"Restrict access by allowing or blocking peer network ranges."
|
||||
}
|
||||
iconClass={"bg-gradient-to-tr from-blue-500 to-blue-400"}
|
||||
active={value !== undefined}
|
||||
onReset={() => onChange(undefined)}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
key={open ? 1 : 0}
|
||||
icon={<NetworkIcon size={16} />}
|
||||
title={t("peerNetworkRange")}
|
||||
modalWidthClass={"max-w-xl"}
|
||||
description={t("peerNetworkRangeHelp")}
|
||||
iconClass={"bg-gradient-to-tr from-blue-500 to-blue-400"}
|
||||
active={value !== undefined}
|
||||
onReset={() => onChange(undefined)}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
};
|
||||
|
||||
interface NetworkRange {
|
||||
id: string;
|
||||
value: string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const CheckContent = ({ value, onChange, disabled }: Props) => {
|
||||
const t = useTranslations("common");
|
||||
const [allowOrDeny, setAllowOrDeny] = useState<string>(
|
||||
value?.action ? value.action : "allow",
|
||||
);
|
||||
const t = useTranslations("postureChecks");
|
||||
const tCommon = useTranslations("common");
|
||||
const [allowOrDeny, setAllowOrDeny] = useState<string>(
|
||||
value?.action ? value.action : "allow",
|
||||
);
|
||||
|
||||
const [networkRanges, setNetworkRanges] = useState<NetworkRange[]>(
|
||||
value?.ranges
|
||||
? value.ranges.map((r) => {
|
||||
return {
|
||||
id: uniqueId("range"),
|
||||
value: r,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
);
|
||||
const [networkRanges, setNetworkRanges] = useState<NetworkRange[]>(
|
||||
value?.ranges
|
||||
? value.ranges.map((r) => {
|
||||
return {
|
||||
id: uniqueId("range"),
|
||||
value: r,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
);
|
||||
|
||||
const handleNetworkRangeChange = (id: string, value: string) => {
|
||||
const newRanges = networkRanges.map((r) =>
|
||||
r.id === id ? { ...r, value } : r,
|
||||
);
|
||||
setNetworkRanges(newRanges);
|
||||
};
|
||||
const handleNetworkRangeChange = (id: string, value: string) => {
|
||||
const newRanges = networkRanges.map((r) =>
|
||||
r.id === id ? { ...r, value } : r,
|
||||
);
|
||||
setNetworkRanges(newRanges);
|
||||
};
|
||||
|
||||
const removeNetworkRange = (id: string) => {
|
||||
const newRanges = networkRanges.filter((r) => r.id !== id);
|
||||
setNetworkRanges(newRanges);
|
||||
};
|
||||
const removeNetworkRange = (id: string) => {
|
||||
const newRanges = networkRanges.filter((r) => r.id !== id);
|
||||
setNetworkRanges(newRanges);
|
||||
};
|
||||
|
||||
const addNetworkRange = () => {
|
||||
setNetworkRanges([...networkRanges, { id: uniqueId("range"), value: "" }]);
|
||||
};
|
||||
const addNetworkRange = () => {
|
||||
setNetworkRanges([...networkRanges, { id: uniqueId("range"), value: "" }]);
|
||||
};
|
||||
|
||||
const validateNetworkRange = (networkRange: string) => {
|
||||
if (networkRange == "") return "";
|
||||
const validCIDR = cidr.isValidAddress(networkRange);
|
||||
if (!validCIDR) return "Please enter a valid CIDR, e.g., 192.168.1.0/24";
|
||||
return "";
|
||||
};
|
||||
const validateNetworkRange = (networkRange: string) => {
|
||||
if (networkRange == "") return "";
|
||||
const validCIDR = cidr.isValidAddress(networkRange);
|
||||
if (!validCIDR) return t("validCidr");
|
||||
return "";
|
||||
};
|
||||
|
||||
const cidrErrors = useMemo(() => {
|
||||
if (networkRanges && networkRanges.length > 0) {
|
||||
return networkRanges.map((r) => {
|
||||
return {
|
||||
id: r.id,
|
||||
error: validateNetworkRange(r.value),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [networkRanges]);
|
||||
const cidrErrors = useMemo(() => {
|
||||
if (networkRanges && networkRanges.length > 0) {
|
||||
return networkRanges.map((r) => {
|
||||
return {
|
||||
id: r.id,
|
||||
error: validateNetworkRange(r.value),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [networkRanges]);
|
||||
|
||||
const hasErrorsOrIsEmpty = useMemo(() => {
|
||||
if (networkRanges.length === 0) return true;
|
||||
return cidrErrors.some((e) => e.error !== "");
|
||||
}, [networkRanges, cidrErrors]);
|
||||
const hasErrorsOrIsEmpty = useMemo(() => {
|
||||
if (networkRanges.length === 0) return true;
|
||||
return cidrErrors.some((e) => e.error !== "");
|
||||
}, [networkRanges, cidrErrors]);
|
||||
|
||||
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>Allow or Block Ranges</Label>
|
||||
<HelpText className={""}>
|
||||
Choose whether you want to allow or block specific peer network
|
||||
ranges
|
||||
</HelpText>
|
||||
</div>
|
||||
<RadioGroup value={allowOrDeny} onChange={setAllowOrDeny}>
|
||||
<RadioGroupItem value={"allow"} variant={"green"}>
|
||||
<ShieldCheck size={16} />
|
||||
Allow
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={"deny"} variant={"red"}>
|
||||
<ShieldXIcon size={16} />
|
||||
Block
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{networkRanges.length > 0 && (
|
||||
<div className={"mb-2 flex flex-col gap-2 w-full "}>
|
||||
{networkRanges.map((ipRange) => {
|
||||
return (
|
||||
<div key={ipRange.id} className={"flex gap-2"}>
|
||||
<div className={"w-full"}>
|
||||
<Input
|
||||
customPrefix={<NetworkIcon size={16} />}
|
||||
placeholder={"e.g., 172.16.0.0/16"}
|
||||
value={ipRange.value}
|
||||
error={cidrErrors.find((e) => e.id === ipRange.id)?.error}
|
||||
errorTooltip={false}
|
||||
className={"font-mono !text-[13px] w-full"}
|
||||
onChange={(e) =>
|
||||
handleNetworkRangeChange(ipRange.id, e.target.value)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
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>{t("allowOrBlockRanges")}</Label>
|
||||
<HelpText className={""}>{t("allowOrBlockRangesHelp")}</HelpText>
|
||||
</div>
|
||||
<RadioGroup value={allowOrDeny} onChange={setAllowOrDeny}>
|
||||
<RadioGroupItem value={"allow"} variant={"green"}>
|
||||
<ShieldCheck size={16} />
|
||||
{tCommon("allow")}
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={"deny"} variant={"red"}>
|
||||
<ShieldXIcon size={16} />
|
||||
{tCommon("block")}
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{networkRanges.length > 0 && (
|
||||
<div className={"mb-2 flex flex-col gap-2 w-full "}>
|
||||
{networkRanges.map((ipRange) => {
|
||||
return (
|
||||
<div key={ipRange.id} className={"flex gap-2"}>
|
||||
<div className={"w-full"}>
|
||||
<Input
|
||||
customPrefix={<NetworkIcon size={16} />}
|
||||
placeholder={t("cidrPlaceholder")}
|
||||
value={ipRange.value}
|
||||
error={cidrErrors.find((e) => e.id === ipRange.id)?.error}
|
||||
errorTooltip={false}
|
||||
className={"font-mono !text-[13px] w-full"}
|
||||
onChange={(e) =>
|
||||
handleNetworkRangeChange(ipRange.id, e.target.value)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={"h-[42px]"}
|
||||
variant={"default-outline"}
|
||||
onClick={() => removeNetworkRange(ipRange.id)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<MinusCircleIcon size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant={"dotted"}
|
||||
size={"sm"}
|
||||
onClick={addNetworkRange}
|
||||
disabled={disabled}
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
Add Network Range
|
||||
</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#peer-network-range-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
Peer Network Range Check
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{t("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
disabled={hasErrorsOrIsEmpty || disabled}
|
||||
onClick={() => {
|
||||
if (isEmpty(networkRanges)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({
|
||||
action: allowOrDeny as "allow" | "deny",
|
||||
ranges: networkRanges
|
||||
.map((r) => r.value)
|
||||
.filter((r) => r !== ""),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
<Button
|
||||
className={"h-[42px]"}
|
||||
variant={"default-outline"}
|
||||
onClick={() => removeNetworkRange(ipRange.id)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<MinusCircleIcon size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant={"dotted"}
|
||||
size={"sm"}
|
||||
onClick={addNetworkRange}
|
||||
disabled={disabled}
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
{t("addNetworkRange")}
|
||||
</Button>
|
||||
</div>
|
||||
<ModalFooter className={"items-center"}>
|
||||
<div className={"w-full"}>
|
||||
<Paragraph className={"text-sm mt-auto"}>
|
||||
{t("learnMoreAbout")}
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/how-to/manage-posture-checks#peer-network-range-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
{t("peerNetworkRangeCheck")}
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
disabled={hasErrorsOrIsEmpty || disabled}
|
||||
onClick={() => {
|
||||
if (isEmpty(networkRanges)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({
|
||||
action: allowOrDeny as "allow" | "deny",
|
||||
ranges: networkRanges
|
||||
.map((r) => r.value)
|
||||
.filter((r) => r !== ""),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{tCommon("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,11 +8,11 @@ import Paragraph from "@components/Paragraph";
|
||||
import { cn, validator } from "@utils/helpers";
|
||||
import { isEmpty, uniqueId } from "lodash";
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
MinusCircleIcon,
|
||||
PlusCircle,
|
||||
ServerCogIcon,
|
||||
TerminalIcon,
|
||||
ExternalLinkIcon,
|
||||
MinusCircleIcon,
|
||||
PlusCircle,
|
||||
ServerCogIcon,
|
||||
TerminalIcon,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
@@ -23,297 +23,291 @@ import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type Props = {
|
||||
value?: ProcessCheck;
|
||||
onChange: (value: ProcessCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
value?: ProcessCheck;
|
||||
onChange: (value: ProcessCheck | undefined) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const PostureCheckProcess = ({ value, onChange, disabled }: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const t = useTranslations("postureChecks");
|
||||
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);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
key={open ? 1 : 0}
|
||||
active={value?.processes && value?.processes?.length > 0}
|
||||
title={t("process")}
|
||||
description={t("processHelp")}
|
||||
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);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckContent = ({ value, onChange, disabled }: Props) => {
|
||||
const t = useTranslations("common");
|
||||
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 t = useTranslations("postureChecks");
|
||||
const tCommon = useTranslations("common");
|
||||
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 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 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 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 pathErrors = useMemo(() => {
|
||||
if (processes && processes.length > 0) {
|
||||
return processes.map((p) => {
|
||||
return {
|
||||
id: p.id,
|
||||
errorMacPath: p?.mac_path
|
||||
? validator.isValidUnixFilePath(p?.mac_path || "")
|
||||
? ""
|
||||
: t("validMacPath")
|
||||
: "",
|
||||
errorLinuxPath: p?.linux_path
|
||||
? validator.isValidUnixFilePath(p?.linux_path || "")
|
||||
? ""
|
||||
: t("validUnixPath")
|
||||
: "",
|
||||
errorWindowsPath: p?.windows_path
|
||||
? validator.isValidWindowsFilePath(p?.windows_path || "")
|
||||
? ""
|
||||
: t("validWindowsPath")
|
||||
: "",
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [processes, t]);
|
||||
|
||||
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]);
|
||||
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 || "",
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<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 || "",
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<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,
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
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>{t("processes")}</Label>
|
||||
<HelpText className={""}>{t("processesHelp")}</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={t("linuxPathPlaceholder")}
|
||||
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 || "",
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Input
|
||||
customPrefix={
|
||||
<AppleIcon
|
||||
size={16}
|
||||
className={cn(
|
||||
pathErrors.find((e) => e.id === p.id)
|
||||
?.errorMacPath && "fill-red-500",
|
||||
)}
|
||||
/>
|
||||
}
|
||||
placeholder={t("macPathPlaceholder")}
|
||||
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 || "",
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Input
|
||||
customPrefix={
|
||||
<WindowsIcon
|
||||
size={16}
|
||||
className={cn(
|
||||
pathErrors.find((e) => e.id === p.id)
|
||||
?.errorWindowsPath && "fill-red-500",
|
||||
)}
|
||||
/>
|
||||
}
|
||||
placeholder={t("windowsPathPlaceholder")}
|
||||
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,
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={"h-[42px]"}
|
||||
variant={"default-outline"}
|
||||
onClick={() => removeProcess(p.id)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<MinusCircleIcon size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant={"dotted"}
|
||||
size={"sm"}
|
||||
onClick={addProcess}
|
||||
className={"mt-1"}
|
||||
disabled={disabled}
|
||||
>
|
||||
<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"}>{t("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
disabled={hasErrorsOrIsEmpty || disabled}
|
||||
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>
|
||||
</>
|
||||
);
|
||||
<Button
|
||||
className={"h-[42px]"}
|
||||
variant={"default-outline"}
|
||||
onClick={() => removeProcess(p.id)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<MinusCircleIcon size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant={"dotted"}
|
||||
size={"sm"}
|
||||
onClick={addProcess}
|
||||
className={"mt-1"}
|
||||
disabled={disabled}
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
{t("addProcess")}
|
||||
</Button>
|
||||
</div>
|
||||
<ModalFooter className={"items-center"}>
|
||||
<div className={"w-full"}>
|
||||
<Paragraph className={"text-sm mt-auto"}>
|
||||
{t("learnMoreAbout")}
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/how-to/manage-posture-checks#process-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
{t("processCheck")}
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>{tCommon("cancel")}</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
disabled={hasErrorsOrIsEmpty || disabled}
|
||||
onClick={() => {
|
||||
if (isEmpty(processes)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({
|
||||
processes: processes.filter(
|
||||
(p) =>
|
||||
p.linux_path !== "" ||
|
||||
p.mac_path !== "" ||
|
||||
p.windows_path !== "",
|
||||
),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{tCommon("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,61 +5,61 @@ import useFetchApi from "@utils/api";
|
||||
import { cn } from "@utils/helpers";
|
||||
import { FolderSearch } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||
import { PostureCheck } from "@/interfaces/PostureCheck";
|
||||
|
||||
export function PostureCheckNoChecksInfo({
|
||||
onAddClick,
|
||||
onBrowseClick,
|
||||
onAddClick,
|
||||
onBrowseClick,
|
||||
}: {
|
||||
onAddClick: () => void;
|
||||
onBrowseClick: () => void;
|
||||
onAddClick: () => void;
|
||||
onBrowseClick: () => void;
|
||||
}) {
|
||||
const { permission } = usePermissions();
|
||||
const t = useTranslations("postureChecks");
|
||||
const { permission } = usePermissions();
|
||||
|
||||
const { data: postureChecks } =
|
||||
useFetchApi<PostureCheck[]>("/posture-checks");
|
||||
const { data: postureChecks } =
|
||||
useFetchApi<PostureCheck[]>("/posture-checks");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={
|
||||
"mx-auto text-center flex flex-col items-center justify-center"
|
||||
}
|
||||
>
|
||||
<h2 className={"text-lg my-0 leading-[1.5 text-center]"}>
|
||||
{"You haven't added any posture checks yet"}
|
||||
</h2>
|
||||
<Paragraph className={cn("text-sm text-center max-w-md mt-1")}>
|
||||
Add various posture checks to further restrict access in your network.
|
||||
E.g., only clients with a specific NetBird client version, operating
|
||||
system or location are allowed to connect.
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex items-center justify-center gap-4 mt-5"}>
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
size={"xs"}
|
||||
disabled={
|
||||
postureChecks?.length == 0 ||
|
||||
!permission.policies.create ||
|
||||
!permission.policies.update
|
||||
}
|
||||
onClick={onBrowseClick}
|
||||
>
|
||||
<FolderSearch size={14} />
|
||||
Browse Checks
|
||||
</Button>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
size={"xs"}
|
||||
onClick={onAddClick}
|
||||
disabled={!permission.policies.create || !permission.policies.update}
|
||||
>
|
||||
<IconCirclePlus size={14} />
|
||||
New Posture Check
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={
|
||||
"mx-auto text-center flex flex-col items-center justify-center"
|
||||
}
|
||||
>
|
||||
<h2 className={"text-lg my-0 leading-[1.5 text-center]"}>
|
||||
{t("noChecks")}
|
||||
</h2>
|
||||
<Paragraph className={cn("text-sm text-center max-w-md mt-1")}>
|
||||
{t("noChecksDescription")}
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex items-center justify-center gap-4 mt-5"}>
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
size={"xs"}
|
||||
disabled={
|
||||
postureChecks?.length == 0 ||
|
||||
!permission.policies.create ||
|
||||
!permission.policies.update
|
||||
}
|
||||
onClick={onBrowseClick}
|
||||
>
|
||||
<FolderSearch size={14} />
|
||||
{t("browseChecks")}
|
||||
</Button>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
size={"xs"}
|
||||
onClick={onAddClick}
|
||||
disabled={!permission.policies.create || !permission.policies.update}
|
||||
>
|
||||
<IconCirclePlus size={14} />
|
||||
{t("newPostureCheck")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user