mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
Add process posture check (#378)
* Add process posture check * Add support for separate linux and mac paths
This commit is contained in:
@@ -13,6 +13,7 @@ export interface InputProps
|
||||
icon?: React.ReactNode;
|
||||
error?: string;
|
||||
errorTooltip?: boolean;
|
||||
errorTooltipPosition?: "top" | "top-right";
|
||||
}
|
||||
|
||||
const inputVariants = cva("", {
|
||||
@@ -49,6 +50,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
maxWidthClass = "",
|
||||
error,
|
||||
errorTooltip = false,
|
||||
errorTooltipPosition = "top",
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
@@ -105,9 +107,12 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
</div>
|
||||
{error && errorTooltip && (
|
||||
<div
|
||||
className={
|
||||
"absolute right-0 top-2 h-[0px] w-full flex items-center pr-3 justify-center"
|
||||
}
|
||||
className={cn(
|
||||
errorTooltipPosition == "top" &&
|
||||
"absolute right-0 top-2 h-[0px] w-full flex items-center pr-3 justify-center",
|
||||
errorTooltipPosition == "top-right" &&
|
||||
"absolute -right-6 top-2 h-[0px] w-full flex items-center pr-3 justify-end",
|
||||
)}
|
||||
>
|
||||
<FullTooltip
|
||||
content={
|
||||
@@ -120,7 +125,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
</div>
|
||||
}
|
||||
interactive={false}
|
||||
align={"center"}
|
||||
align={errorTooltipPosition == "top" ? "center" : "end"}
|
||||
side={"top"}
|
||||
keepOpen={true}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface PostureCheck {
|
||||
os_version_check?: OperatingSystemVersionCheck;
|
||||
geo_location_check?: GeoLocationCheck;
|
||||
peer_network_range_check?: PeerNetworkRangeCheck;
|
||||
process_check?: ProcessCheck;
|
||||
};
|
||||
policies?: Policy[];
|
||||
active?: boolean;
|
||||
@@ -53,6 +54,17 @@ export interface PeerNetworkRangeCheck {
|
||||
action: "allow" | "deny";
|
||||
}
|
||||
|
||||
export interface ProcessCheck {
|
||||
processes: Process[];
|
||||
}
|
||||
|
||||
export interface Process {
|
||||
id: string;
|
||||
linux_path?: string;
|
||||
mac_path?: string;
|
||||
windows_path?: string;
|
||||
}
|
||||
|
||||
export const windowsKernelVersions: SelectOption[] = [
|
||||
{ value: "5.0", label: "Windows 2000" },
|
||||
{ value: "5.1", label: "Windows XP" },
|
||||
|
||||
310
src/modules/posture-checks/checks/PostureCheckProcess.tsx
Normal file
310
src/modules/posture-checks/checks/PostureCheckProcess.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
import Button from "@components/Button";
|
||||
import HelpText from "@components/HelpText";
|
||||
import InlineLink from "@components/InlineLink";
|
||||
import { Input } from "@components/Input";
|
||||
import { Label } from "@components/Label";
|
||||
import { ModalClose, ModalFooter } from "@components/modal/Modal";
|
||||
import Paragraph from "@components/Paragraph";
|
||||
import { cn, validator } from "@utils/helpers";
|
||||
import { isEmpty, uniqueId } from "lodash";
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
MinusCircleIcon,
|
||||
PlusCircle,
|
||||
ServerCogIcon,
|
||||
TerminalIcon,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import AppleIcon from "@/assets/icons/AppleIcon";
|
||||
import WindowsIcon from "@/assets/icons/WindowsIcon";
|
||||
import { Process, ProcessCheck } from "@/interfaces/PostureCheck";
|
||||
import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard";
|
||||
|
||||
type Props = {
|
||||
value?: ProcessCheck;
|
||||
onChange: (value: ProcessCheck | undefined) => void;
|
||||
};
|
||||
|
||||
export const PostureCheckProcess = ({ value, onChange }: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<PostureCheckCard
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
key={open ? 1 : 0}
|
||||
active={value?.processes && value?.processes?.length > 0}
|
||||
title={"Process"}
|
||||
description={
|
||||
"Restrict access in your network based on running processes of a peer."
|
||||
}
|
||||
icon={<ServerCogIcon size={18} />}
|
||||
iconClass={"bg-gradient-to-tr from-nb-gray-500 to-nb-gray-300"}
|
||||
modalWidthClass={"max-w-xl"}
|
||||
onReset={() => onChange(undefined)}
|
||||
>
|
||||
<CheckContent
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</PostureCheckCard>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckContent = ({ value, onChange }: Props) => {
|
||||
const [processes, setProcesses] = useState<Process[]>(
|
||||
value?.processes
|
||||
? value.processes.map((p) => {
|
||||
return {
|
||||
id: uniqueId("process"),
|
||||
linux_path: p?.linux_path || "",
|
||||
mac_path: p?.mac_path || "",
|
||||
windows_path: p?.windows_path || "",
|
||||
};
|
||||
})
|
||||
: [
|
||||
{
|
||||
id: uniqueId("process"),
|
||||
linux_path: "",
|
||||
mac_path: "",
|
||||
windows_path: "",
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const handleProcessChange = (
|
||||
id: string,
|
||||
linux_path: string,
|
||||
mac_path: string,
|
||||
windows_path: string,
|
||||
) => {
|
||||
const newProcesses = processes.map((p) =>
|
||||
p.id === id ? { ...p, linux_path, mac_path, windows_path } : p,
|
||||
);
|
||||
setProcesses(newProcesses);
|
||||
};
|
||||
|
||||
const removeProcess = (id: string) => {
|
||||
const newProcesses = processes.filter((p) => p.id !== id);
|
||||
setProcesses(newProcesses);
|
||||
};
|
||||
|
||||
const addProcess = () => {
|
||||
setProcesses([
|
||||
...processes,
|
||||
{
|
||||
id: uniqueId("process"),
|
||||
linux_path: "",
|
||||
mac_path: "",
|
||||
windows_path: "",
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const pathErrors = useMemo(() => {
|
||||
if (processes && processes.length > 0) {
|
||||
return processes.map((p) => {
|
||||
return {
|
||||
id: p.id,
|
||||
errorMacPath: p?.mac_path
|
||||
? validator.isValidUnixFilePath(p?.mac_path || "")
|
||||
? ""
|
||||
: "Please enter a valid macOS file path"
|
||||
: "",
|
||||
errorLinuxPath: p?.linux_path
|
||||
? validator.isValidUnixFilePath(p?.linux_path || "")
|
||||
? ""
|
||||
: "Please enter a valid Unix file path"
|
||||
: "",
|
||||
errorWindowsPath: p?.windows_path
|
||||
? validator.isValidWindowsFilePath(p?.windows_path || "")
|
||||
? ""
|
||||
: "Please enter a valid Windows file path"
|
||||
: "",
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [processes]);
|
||||
|
||||
const hasErrorsOrIsEmpty = useMemo(() => {
|
||||
if (processes.length === 0) return true;
|
||||
const hasOnlyEmptyPaths = processes.some(
|
||||
(p) => p.linux_path === "" && p.mac_path === "" && p.windows_path === "",
|
||||
);
|
||||
const hasPathErrors = pathErrors.some(
|
||||
(e) =>
|
||||
e.errorLinuxPath !== "" ||
|
||||
e.errorMacPath !== "" ||
|
||||
e.errorWindowsPath !== "",
|
||||
);
|
||||
return hasOnlyEmptyPaths || hasPathErrors;
|
||||
}, [processes, pathErrors]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={"flex flex-col px-8 gap-2 pb-6"}>
|
||||
<div className={"flex justify-between items-start gap-10 mt-2"}>
|
||||
<div>
|
||||
<Label>Processes</Label>
|
||||
<HelpText className={""}>
|
||||
Add the path of an executable file of the process. You can define
|
||||
a path for Linux, macOS and Windows. Peers will only be allowed to
|
||||
connect if the process is running on their system.
|
||||
</HelpText>
|
||||
</div>
|
||||
</div>
|
||||
{processes.length > 0 && (
|
||||
<div className={"mb-2 flex flex-col gap-4 w-full "}>
|
||||
{processes.map((p) => {
|
||||
return (
|
||||
<div key={p.id} className={"flex gap-2 items-center"}>
|
||||
<div className={"w-full flex flex-col gap-1.5"}>
|
||||
<Input
|
||||
customPrefix={<TerminalIcon size={16} />}
|
||||
placeholder={"/usr/local/bin/netbird"}
|
||||
value={p.linux_path}
|
||||
error={
|
||||
pathErrors.find((e) => e.id === p.id)?.errorLinuxPath
|
||||
}
|
||||
errorTooltip={true}
|
||||
errorTooltipPosition={"top-right"}
|
||||
className={"w-full"}
|
||||
onChange={(e) =>
|
||||
handleProcessChange(
|
||||
p.id,
|
||||
e.target.value,
|
||||
p?.mac_path || "",
|
||||
p?.windows_path || "",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
customPrefix={
|
||||
<AppleIcon
|
||||
size={16}
|
||||
className={cn(
|
||||
pathErrors.find((e) => e.id === p.id)
|
||||
?.errorMacPath && "fill-red-500",
|
||||
)}
|
||||
/>
|
||||
}
|
||||
placeholder={
|
||||
"/Applications/NetBird.app/Contents/MacOS/netbird"
|
||||
}
|
||||
value={p.mac_path}
|
||||
error={
|
||||
pathErrors.find((e) => e.id === p.id)?.errorMacPath
|
||||
}
|
||||
errorTooltip={true}
|
||||
errorTooltipPosition={"top-right"}
|
||||
className={"w-full"}
|
||||
onChange={(e) =>
|
||||
handleProcessChange(
|
||||
p.id,
|
||||
p?.linux_path || "",
|
||||
e.target.value,
|
||||
p?.windows_path || "",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
customPrefix={
|
||||
<WindowsIcon
|
||||
size={16}
|
||||
className={cn(
|
||||
pathErrors.find((e) => e.id === p.id)
|
||||
?.errorWindowsPath && "fill-red-500",
|
||||
)}
|
||||
/>
|
||||
}
|
||||
placeholder={`C:\\ProgramData\\NetBird\\netbird.exe`}
|
||||
value={p.windows_path}
|
||||
errorTooltip={true}
|
||||
errorTooltipPosition={"top-right"}
|
||||
error={
|
||||
pathErrors.find((e) => e.id === p.id)?.errorWindowsPath
|
||||
}
|
||||
className={"w-full"}
|
||||
onChange={(e) =>
|
||||
handleProcessChange(
|
||||
p.id,
|
||||
p?.linux_path || "",
|
||||
p?.mac_path || "",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={"h-[42px]"}
|
||||
variant={"default-outline"}
|
||||
onClick={() => removeProcess(p.id)}
|
||||
>
|
||||
<MinusCircleIcon size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant={"dotted"}
|
||||
size={"sm"}
|
||||
onClick={addProcess}
|
||||
className={"mt-1"}
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
Add Process
|
||||
</Button>
|
||||
</div>
|
||||
<ModalFooter className={"items-center"}>
|
||||
<div className={"w-full"}>
|
||||
<Paragraph className={"text-sm mt-auto"}>
|
||||
Learn more about
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/how-to/manage-posture-checks#process-check"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
Process Check
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className={"flex gap-3 w-full justify-end"}>
|
||||
<ModalClose asChild={true}>
|
||||
<Button variant={"secondary"}>Cancel</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
disabled={hasErrorsOrIsEmpty}
|
||||
onClick={() => {
|
||||
if (isEmpty(processes)) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({
|
||||
processes: processes.filter(
|
||||
(p) =>
|
||||
p.linux_path !== "" ||
|
||||
p.mac_path !== "" ||
|
||||
p.windows_path !== "",
|
||||
),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
112
src/modules/posture-checks/checks/tooltips/ProcessTooltip.tsx
Normal file
112
src/modules/posture-checks/checks/tooltips/ProcessTooltip.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import Badge from "@components/Badge";
|
||||
import FullTooltip from "@components/FullTooltip";
|
||||
import { ScrollArea } from "@components/ScrollArea";
|
||||
import { tryGetProcessNameFromPath } from "@utils/helpers";
|
||||
import { TerminalIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import AppleIcon from "@/assets/icons/AppleIcon";
|
||||
import WindowsIcon from "@/assets/icons/WindowsIcon";
|
||||
import { ProcessCheck } from "@/interfaces/PostureCheck";
|
||||
|
||||
type Props = {
|
||||
check?: ProcessCheck;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const ProcessTooltip = ({ check, children }: Props) => {
|
||||
return check ? (
|
||||
<FullTooltip
|
||||
className={"w-full min-w-0"}
|
||||
interactive={true}
|
||||
contentClassName={"p-0"}
|
||||
content={
|
||||
<div
|
||||
className={
|
||||
"text-neutral-300 text-sm max-w-xs flex flex-col gap-1 min-w-0"
|
||||
}
|
||||
>
|
||||
<div className={"px-4 pt-3"}>
|
||||
<span>
|
||||
<span className={"text-green-500 font-semibold"}>Allow only</span>{" "}
|
||||
peers which are running the following processes
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ScrollArea
|
||||
className={
|
||||
"max-h-[275px] overflow-y-auto flex flex-col px-4 min-w-0"
|
||||
}
|
||||
>
|
||||
<div className={"flex flex-col gap-3 mt-1 text-xs mb-3.5 min-w-0"}>
|
||||
{check.processes.map((p, index) => {
|
||||
return (
|
||||
<div className={"flex-col flex gap-1 min-w-0"} key={index}>
|
||||
{p?.linux_path && (
|
||||
<Badge
|
||||
variant={"gray"}
|
||||
useHover={false}
|
||||
className={"justify-start font-medium text-xs min-w-0"}
|
||||
>
|
||||
<span className={"mr-1.5"}>
|
||||
<TerminalIcon size={12} />
|
||||
</span>
|
||||
<span
|
||||
className={"truncate inline-block "}
|
||||
title={p?.linux_path}
|
||||
>
|
||||
{tryGetProcessNameFromPath(p?.linux_path) ||
|
||||
"Unknown path"}
|
||||
</span>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{p?.mac_path && (
|
||||
<Badge
|
||||
variant={"gray"}
|
||||
useHover={false}
|
||||
className={"justify-start font-medium text-xs min-w-0"}
|
||||
>
|
||||
<span className={"mr-1.5"}>
|
||||
<AppleIcon size={12} />
|
||||
</span>
|
||||
<span
|
||||
className={"truncate inline-block "}
|
||||
title={p?.mac_path}
|
||||
>
|
||||
{tryGetProcessNameFromPath(p?.mac_path) ||
|
||||
"Unknown path"}
|
||||
</span>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{p?.windows_path && (
|
||||
<Badge
|
||||
variant={"gray"}
|
||||
useHover={false}
|
||||
className={"justify-start font-medium text-xs min-w-0"}
|
||||
>
|
||||
<span className={"mr-1.5"}>
|
||||
<WindowsIcon size={12} />
|
||||
</span>
|
||||
<span
|
||||
className={"truncate inline-block"}
|
||||
title={p?.windows_path}
|
||||
>
|
||||
{tryGetProcessNameFromPath(p?.windows_path) ||
|
||||
"Unknown path"}
|
||||
</span>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</FullTooltip>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
};
|
||||
@@ -24,6 +24,7 @@ import { PostureCheckGeoLocation } from "@/modules/posture-checks/checks/Posture
|
||||
import { PostureCheckNetBirdVersion } from "@/modules/posture-checks/checks/PostureCheckNetBirdVersion";
|
||||
import { PostureCheckOperatingSystem } from "@/modules/posture-checks/checks/PostureCheckOperatingSystem";
|
||||
import { PostureCheckPeerNetworkRange } from "@/modules/posture-checks/checks/PostureCheckPeerNetworkRange";
|
||||
import { PostureCheckProcess } from "@/modules/posture-checks/checks/PostureCheckProcess";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
@@ -58,6 +59,9 @@ export default function PostureCheckModal({
|
||||
const [peerNetworkRangeCheck, setPeerNetworkRangeCheck] = useState(
|
||||
postureCheck?.checks.peer_network_range_check || undefined,
|
||||
);
|
||||
const [processCheck, setProcessCheck] = useState(
|
||||
postureCheck?.checks.process_check || undefined,
|
||||
);
|
||||
|
||||
const validateOSCheck = (osCheck?: OperatingSystemVersionCheck) => {
|
||||
if (!osCheck) return;
|
||||
@@ -98,6 +102,7 @@ export default function PostureCheckModal({
|
||||
geo_location_check: validateLocationCheck(geoLocationCheck),
|
||||
os_version_check: validateOSCheck(osVersionCheck),
|
||||
peer_network_range_check: peerNetworkRangeCheck,
|
||||
process_check: processCheck,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -133,7 +138,8 @@ export default function PostureCheckModal({
|
||||
!!nbVersionCheck ||
|
||||
!!geoLocationCheck ||
|
||||
!!osVersionCheck ||
|
||||
!!peerNetworkRangeCheck;
|
||||
!!peerNetworkRangeCheck ||
|
||||
!!processCheck;
|
||||
const canCreate = !isEmpty(name) && isAtLeastOneCheckEnabled;
|
||||
|
||||
const [tab, setTab] = useState("checks");
|
||||
@@ -187,13 +193,17 @@ export default function PostureCheckModal({
|
||||
value={geoLocationCheck}
|
||||
onChange={setGeoLocationCheckCheck}
|
||||
/>
|
||||
<PostureCheckPeerNetworkRange
|
||||
value={peerNetworkRangeCheck}
|
||||
onChange={setPeerNetworkRangeCheck}
|
||||
/>
|
||||
<PostureCheckOperatingSystem
|
||||
value={osVersionCheck}
|
||||
onChange={setOsVersionCheck}
|
||||
/>
|
||||
<PostureCheckPeerNetworkRange
|
||||
value={peerNetworkRangeCheck}
|
||||
onChange={setPeerNetworkRangeCheck}
|
||||
<PostureCheckProcess
|
||||
value={processCheck}
|
||||
onChange={setProcessCheck}
|
||||
/>
|
||||
</>
|
||||
</TabsContent>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn } from "@utils/helpers";
|
||||
import { Disc3Icon, FlagIcon, NetworkIcon } from "lucide-react";
|
||||
import { Disc3Icon, FlagIcon, NetworkIcon, ServerCogIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import NetBirdIcon from "@/assets/icons/NetBirdIcon";
|
||||
import { PostureCheck } from "@/interfaces/PostureCheck";
|
||||
@@ -7,6 +7,7 @@ import { GeoLocationTooltip } from "@/modules/posture-checks/checks/tooltips/Geo
|
||||
import { NetBirdVersionTooltip } from "@/modules/posture-checks/checks/tooltips/NetBirdVersionTooltip";
|
||||
import { OperatingSystemTooltip } from "@/modules/posture-checks/checks/tooltips/OperatingSystemTooltip";
|
||||
import { PeerNetworkRangeTooltip } from "@/modules/posture-checks/checks/tooltips/PeerNetworkRangeTooltip";
|
||||
import { ProcessTooltip } from "@/modules/posture-checks/checks/tooltips/ProcessTooltip";
|
||||
|
||||
type Props = {
|
||||
check: PostureCheck;
|
||||
@@ -71,6 +72,18 @@ export const PostureCheckChecksCell = ({ check }: Props) => {
|
||||
</div>
|
||||
</PeerNetworkRangeTooltip>
|
||||
)}
|
||||
|
||||
{check.checks.process_check && (
|
||||
<ProcessTooltip check={check.checks.process_check}>
|
||||
<div
|
||||
className={cn(
|
||||
"bg-gradient-to-tr from-nb-gray-500 to-nb-gray-300 h-8 w-8 rounded-full flex items-center justify-center relative z-[8] hover:scale-[1.1] transition-all",
|
||||
)}
|
||||
>
|
||||
<ServerCogIcon size={14} />
|
||||
</div>
|
||||
</ProcessTooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,10 @@ export const PostureCheckPolicyUsageCell = ({ check }: Props) => {
|
||||
interactive={false}
|
||||
>
|
||||
<Badge
|
||||
onClick={() => router.push("/access-control")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
router.push("/access-control");
|
||||
}}
|
||||
variant={"gray"}
|
||||
useHover={!!(check.policies && check.policies?.length > 0)}
|
||||
className={cn(
|
||||
|
||||
@@ -74,8 +74,37 @@ export const validator = {
|
||||
/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
|
||||
return semverRegex.test(version);
|
||||
},
|
||||
isValidUnixFilePath: (path: string) => {
|
||||
const endsWithSlash = path.endsWith("/");
|
||||
const unixPathRegex = /^\/(?:[^/]+\/)*[^/]+$/;
|
||||
const isValid = unixPathRegex.test(path);
|
||||
return isValid && !endsWithSlash;
|
||||
},
|
||||
isValidWindowsFilePath: (path: string) => {
|
||||
const endsWithBackSlash = path.endsWith("\\");
|
||||
const windowsPathRegex =
|
||||
/^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$/;
|
||||
const isValid = windowsPathRegex.test(path);
|
||||
return isValid && !endsWithBackSlash;
|
||||
},
|
||||
};
|
||||
|
||||
export function isInt(n: number) {
|
||||
return n % 1 === 0;
|
||||
}
|
||||
|
||||
export function tryGetProcessNameFromPath(path: string) {
|
||||
try {
|
||||
const canSplitByForwardSlash = path.includes("/");
|
||||
const canSplitByBackSlash = path.includes("\\");
|
||||
const byForwardSlash = canSplitByForwardSlash
|
||||
? path.split("/").pop()
|
||||
: undefined;
|
||||
const byBackSlash = canSplitByBackSlash
|
||||
? path.split("\\").pop()
|
||||
: undefined;
|
||||
return byForwardSlash || byBackSlash || path;
|
||||
} catch (e) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user