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