mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8c30f95c7 |
No files matched your search
@@ -91,6 +91,7 @@
|
||||
"@types/chroma-js": "^3.1.1",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"eslint-config-next": "^14.2.28",
|
||||
"cypress": "^13.13.0",
|
||||
"postcss": "^8",
|
||||
"prettier": "3.0.3",
|
||||
"tailwindcss": "^3"
|
||||
|
||||
@@ -4,18 +4,7 @@ import md5 from "crypto-js/md5";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||
|
||||
const initialAnnouncements: Announcement[] = [
|
||||
{
|
||||
tag: "New",
|
||||
text: "NetBird v0.60.0 - Identity-aware, private SSH over your NetBird network.",
|
||||
link: "https://docs.netbird.io/how-to/ssh",
|
||||
linkText: "Documentation",
|
||||
variant: "default", // "default" or "important"
|
||||
isExternal: true,
|
||||
closeable: true,
|
||||
isCloudOnly: false,
|
||||
},
|
||||
];
|
||||
const initialAnnouncements: Announcement[] = [];
|
||||
|
||||
export interface Announcement extends AnnouncementVariant {
|
||||
tag: string;
|
||||
|
||||
@@ -138,7 +138,6 @@ export default function PeerProvider({
|
||||
<PeerSSHInstructions
|
||||
open={sshInstructionsModal}
|
||||
onOpenChange={setSSHInstructionsModal}
|
||||
peer={peer}
|
||||
onSuccess={() => toggleSSH(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -45,7 +45,7 @@ import React, { useMemo, useState } from "react";
|
||||
import AccessControlIcon from "@/assets/icons/AccessControlIcon";
|
||||
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||
import { Group } from "@/interfaces/Group";
|
||||
import { Policy, PolicyRuleResource, Protocol } from "@/interfaces/Policy";
|
||||
import { Policy, Protocol } from "@/interfaces/Policy";
|
||||
import { PostureCheck } from "@/interfaces/PostureCheck";
|
||||
import { useAccessControl } from "@/modules/access-control/useAccessControl";
|
||||
import { PostureCheckTab } from "@/modules/posture-checks/ui/PostureCheckTab";
|
||||
@@ -116,9 +116,6 @@ type ModalProps = {
|
||||
postureCheckTemplates?: PostureCheck[];
|
||||
useSave?: boolean;
|
||||
allowEditPeers?: boolean;
|
||||
initialProtocol?: Protocol;
|
||||
initialPorts?: number[];
|
||||
initialDestinationResource?: PolicyRuleResource;
|
||||
};
|
||||
|
||||
export function AccessControlModalContent({
|
||||
@@ -131,9 +128,6 @@ export function AccessControlModalContent({
|
||||
initialDestinationGroups,
|
||||
initialName,
|
||||
initialDescription,
|
||||
initialProtocol,
|
||||
initialPorts,
|
||||
initialDestinationResource,
|
||||
}: Readonly<ModalProps>) {
|
||||
const { permission } = usePermissions();
|
||||
|
||||
@@ -176,9 +170,6 @@ export function AccessControlModalContent({
|
||||
initialDestinationGroups,
|
||||
initialName,
|
||||
initialDescription,
|
||||
initialPorts,
|
||||
initialProtocol,
|
||||
initialDestinationResource,
|
||||
});
|
||||
|
||||
const [tab, setTab] = useState(() => {
|
||||
|
||||
@@ -6,12 +6,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useSWRConfig } from "swr";
|
||||
import { usePolicies } from "@/contexts/PoliciesProvider";
|
||||
import { Group } from "@/interfaces/Group";
|
||||
import {
|
||||
Policy,
|
||||
PolicyRuleResource,
|
||||
PortRange,
|
||||
Protocol,
|
||||
} from "@/interfaces/Policy";
|
||||
import { Policy, PortRange, Protocol } from "@/interfaces/Policy";
|
||||
import { PostureCheck } from "@/interfaces/PostureCheck";
|
||||
import useGroupHelper from "@/modules/groups/useGroupHelper";
|
||||
import { usePostureCheck } from "@/modules/posture-checks/usePostureCheck";
|
||||
@@ -23,9 +18,6 @@ type Props = {
|
||||
initialDestinationGroups?: Group[] | string[];
|
||||
initialName?: string;
|
||||
initialDescription?: string;
|
||||
initialProtocol?: Protocol;
|
||||
initialPorts?: number[];
|
||||
initialDestinationResource?: PolicyRuleResource;
|
||||
};
|
||||
|
||||
// TODO add reducer
|
||||
@@ -37,9 +29,6 @@ export const useAccessControl = ({
|
||||
initialName,
|
||||
initialDescription,
|
||||
onSuccess,
|
||||
initialProtocol,
|
||||
initialPorts,
|
||||
initialDestinationResource,
|
||||
}: Props = {}) => {
|
||||
const { data: allPostureChecks, isLoading: isPostureChecksLoading } =
|
||||
useFetchApi<PostureCheck[]>("/posture-checks");
|
||||
@@ -86,7 +75,6 @@ export const useAccessControl = ({
|
||||
const [enabled, setEnabled] = useState<boolean>(policy?.enabled ?? true);
|
||||
|
||||
const [ports, setPorts] = useState<number[]>(() => {
|
||||
if (initialPorts) return initialPorts;
|
||||
if (!firstRule) return [];
|
||||
if (firstRule.ports == undefined) return [];
|
||||
if (firstRule.ports.length > 0) {
|
||||
@@ -105,7 +93,7 @@ export const useAccessControl = ({
|
||||
});
|
||||
|
||||
const [protocol, setProtocol] = useState<Protocol>(
|
||||
firstRule ? firstRule.protocol : initialProtocol ?? "all",
|
||||
firstRule ? firstRule.protocol : "all",
|
||||
);
|
||||
const [direction, setDirection] = useState<Direction>(() => {
|
||||
if (!firstRule) return "bi";
|
||||
@@ -143,7 +131,7 @@ export const useAccessControl = ({
|
||||
);
|
||||
|
||||
const [destinationResource, setDestinationResource] = useState(
|
||||
firstRule?.destinationResource ?? initialDestinationResource,
|
||||
firstRule?.destinationResource,
|
||||
);
|
||||
|
||||
const { updateOrCreateAndNotify: checkToCreate } = usePostureCheck({});
|
||||
|
||||
@@ -9,37 +9,26 @@ import {
|
||||
} from "@components/modal/Modal";
|
||||
import ModalHeader from "@components/modal/ModalHeader";
|
||||
import Paragraph from "@components/Paragraph";
|
||||
import { SegmentedTabs } from "@components/SegmentedTabs";
|
||||
import Separator from "@components/Separator";
|
||||
import Steps from "@components/Steps";
|
||||
import { Lightbox } from "@components/ui/Lightbox";
|
||||
import { Mark } from "@components/ui/Mark";
|
||||
import { cn } from "@utils/helpers";
|
||||
import { ExternalLinkIcon, PlusCircle, TerminalSquare } from "lucide-react";
|
||||
import { ExternalLinkIcon, TerminalSquare } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import NetBirdIcon from "@/assets/icons/NetBirdIcon";
|
||||
import sshImage from "@/assets/ssh/ssh-client.png";
|
||||
import { Peer } from "@/interfaces/Peer";
|
||||
import { PeerSSHPolicyModal } from "@/modules/peer/PeerSSHPolicyModal";
|
||||
import { Terminal } from "@/modules/remote-access/ssh/Terminal";
|
||||
|
||||
type Props = {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
onSuccess?: () => void;
|
||||
peer?: Peer;
|
||||
};
|
||||
|
||||
export const PeerSSHInstructions = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
onSuccess,
|
||||
peer,
|
||||
}: Props) => {
|
||||
const [client, setClient] = useState("cli");
|
||||
const [policyModal, setPolicyModal] = useState(false);
|
||||
|
||||
return (
|
||||
<Modal open={open} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
@@ -50,70 +39,36 @@ export const PeerSSHInstructions = ({
|
||||
icon={<TerminalSquare size={16} className={"text-netbird"} />}
|
||||
title={"Enable SSH Access"}
|
||||
description={
|
||||
"Allow remote SSH access from other connected network participants."
|
||||
"Allow remote SSH access to this machine from other connected network participants."
|
||||
}
|
||||
color={"netbird"}
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className={"px-8 py-3 flex flex-col gap-0 z-0 mt-1"}>
|
||||
<SegmentedTabs value={client} onChange={setClient}>
|
||||
<SegmentedTabs.List className={"rounded-lg border"}>
|
||||
<SegmentedTabs.Trigger value={"cli"}>
|
||||
<TerminalSquare size={16} />
|
||||
CLI
|
||||
</SegmentedTabs.Trigger>
|
||||
<SegmentedTabs.Trigger value={"gui"}>
|
||||
<NetBirdIcon size={16} />
|
||||
Desktop Client
|
||||
</SegmentedTabs.Trigger>
|
||||
</SegmentedTabs.List>
|
||||
</SegmentedTabs>
|
||||
|
||||
<div className={"px-8 py-3 flex flex-col gap-0 z-0"}>
|
||||
<Steps>
|
||||
{client === "cli" ? (
|
||||
<Steps.Step step={1}>
|
||||
<p className={"font-normal"}>
|
||||
If you are using NetBird via CLI, you can enable SSH by
|
||||
running
|
||||
</p>
|
||||
<Code codeToCopy={"netbird down"}>
|
||||
<Code.Line>{`netbird down # if NetBird is already running`}</Code.Line>
|
||||
</Code>
|
||||
<Code>
|
||||
<Code.Line>{`netbird up --allow-server-ssh --enable-ssh-root`}</Code.Line>
|
||||
</Code>
|
||||
</Steps.Step>
|
||||
) : (
|
||||
<Steps.Step step={1}>
|
||||
<p className={"font-normal"}>
|
||||
If you are using NetBird via the Desktop Client, click on the
|
||||
NetBird tray icon, go to <Mark>Settings</Mark> and click{" "}
|
||||
<Mark>Allow SSH</Mark>. If you want to enable Root Login go to{" "}
|
||||
<Mark>Settings > Advanced Settings</Mark> and enable SSH
|
||||
Root Login under the SSH tab.
|
||||
</p>
|
||||
<Lightbox image={sshImage} />
|
||||
</Steps.Step>
|
||||
)}
|
||||
<Steps.Step step={1}>
|
||||
<p className={"font-normal"}>
|
||||
If you are using NetBird via CLI, you can enable SSH by running
|
||||
</p>
|
||||
<Code codeToCopy={"netbird down"}>
|
||||
<Code.Line>{`netbird down # if NetBird is already running`}</Code.Line>
|
||||
</Code>
|
||||
<Code>
|
||||
<Code.Line>{`netbird up --allow-server-ssh --enable-ssh-root`}</Code.Line>
|
||||
</Code>
|
||||
</Steps.Step>
|
||||
|
||||
<Steps.Step step={2}>
|
||||
<p className={"font-normal"}>
|
||||
Starting from NetBird v0.60.0, SSH requires an explicit access
|
||||
control policy that allows <Mark>TCP</Mark> traffic on port{" "}
|
||||
<Mark>22</Mark>
|
||||
If you are using NetBird via the Desktop Client, click on the
|
||||
NetBird tray icon, go to <Mark>Settings</Mark> and click{" "}
|
||||
<Mark>Allow SSH</Mark> <br />
|
||||
</p>
|
||||
<div className={"mt-2"}>
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
onClick={() => setPolicyModal(true)}
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
Create SSH Policy
|
||||
</Button>
|
||||
</div>
|
||||
<Lightbox image={sshImage} />
|
||||
</Steps.Step>
|
||||
|
||||
<Steps.Step step={3} line={false}>
|
||||
<p className={"font-normal"}>
|
||||
Once the NetBird SSH server is allowed on the client, <br />
|
||||
@@ -141,17 +96,15 @@ export const PeerSSHInstructions = ({
|
||||
<Button variant={"secondary"}>Cancel</Button>
|
||||
</ModalClose>
|
||||
|
||||
<Button variant={"primary"} onClick={onSuccess}>
|
||||
<Button
|
||||
variant={"primary"}
|
||||
onClick={onSuccess}
|
||||
data-cy={"create-setup-key"}
|
||||
>
|
||||
Confirm & Enable
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
|
||||
<PeerSSHPolicyModal
|
||||
open={policyModal}
|
||||
onOpenChange={setPolicyModal}
|
||||
peer={peer}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Callout } from "@components/Callout";
|
||||
import { InlineButtonLink } from "@components/InlineLink";
|
||||
import { cn } from "@utils/helpers";
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { Peer } from "@/interfaces/Peer";
|
||||
import { PeerSSHPolicyModal } from "@/modules/peer/PeerSSHPolicyModal";
|
||||
import { usePeerSSHPolicyCheck } from "@/modules/peer/usePeerSSHPolicyCheck";
|
||||
|
||||
type Props = {
|
||||
peer?: Peer;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const PeerSSHPolicyInfo = ({ peer, className }: Props) => {
|
||||
const { showSSHPolicyInfo } = usePeerSSHPolicyCheck(peer);
|
||||
const [policyModal, setPolicyModal] = useState(false);
|
||||
return (
|
||||
showSSHPolicyInfo && (
|
||||
<>
|
||||
<Callout className={cn("max-w-xl", className)} variant={"warning"}>
|
||||
<span>
|
||||
Starting from NetBird v0.60.0, SSH requires an explicit access
|
||||
control policy that allows TCP traffic on port 22.{" "}
|
||||
<InlineButtonLink onClick={() => setPolicyModal(true)}>
|
||||
Create SSH Policy
|
||||
</InlineButtonLink>
|
||||
</span>
|
||||
</Callout>
|
||||
<PeerSSHPolicyModal
|
||||
open={policyModal}
|
||||
onOpenChange={setPolicyModal}
|
||||
peer={peer}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Modal } from "@components/modal/Modal";
|
||||
import * as React from "react";
|
||||
import { Peer } from "@/interfaces/Peer";
|
||||
import { PolicyRuleResource } from "@/interfaces/Policy";
|
||||
import { AccessControlModalContent } from "@/modules/access-control/AccessControlModal";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
peer?: Peer;
|
||||
};
|
||||
|
||||
export const PeerSSHPolicyModal = ({ open, onOpenChange, peer }: Props) => {
|
||||
return (
|
||||
<Modal open={open} onOpenChange={onOpenChange}>
|
||||
<AccessControlModalContent
|
||||
key={open ? "1" : "0"}
|
||||
initialPorts={[22]}
|
||||
initialProtocol={"tcp"}
|
||||
initialName={"SSH Access"}
|
||||
initialDestinationResource={
|
||||
peer
|
||||
? ({
|
||||
id: peer.id,
|
||||
type: "peer",
|
||||
} as PolicyRuleResource)
|
||||
: undefined
|
||||
}
|
||||
onSuccess={async (p) => {
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,6 @@ import { LockIcon, TerminalSquare } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { usePeer } from "@/contexts/PeerProvider";
|
||||
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||
import { PeerSSHPolicyInfo } from "@/modules/peer/PeerSSHPolicyInfo";
|
||||
|
||||
export const PeerSSHToggle = () => {
|
||||
const { permission } = usePermissions();
|
||||
@@ -43,7 +42,6 @@ export const PeerSSHToggle = () => {
|
||||
}
|
||||
/>
|
||||
</FullTooltip>
|
||||
<PeerSSHPolicyInfo peer={peer} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
import useFetchApi from "@utils/api";
|
||||
import { Peer } from "@/interfaces/Peer";
|
||||
import { Policy } from "@/interfaces/Policy";
|
||||
import { isNativeSSHSupported } from "@utils/version";
|
||||
|
||||
export const usePeerSSHPolicyCheck = (peer?: Peer) => {
|
||||
const { data: policies, isLoading } = useFetchApi<Policy[]>(
|
||||
"/policies",
|
||||
true,
|
||||
false,
|
||||
);
|
||||
const peerGroupIds = peer?.groups?.map((p) => p.id);
|
||||
|
||||
const peerPolicies = policies?.filter((policy) => {
|
||||
// Skip disabled policies
|
||||
if (!policy?.enabled) return false;
|
||||
|
||||
const rule = policy?.rules?.[0];
|
||||
if (!rule) return false;
|
||||
|
||||
// Skip icmp and udp
|
||||
if (rule.protocol === "icmp" || rule.protocol === "udp") return false;
|
||||
|
||||
// Check resource and groups
|
||||
const isPeerInDestinationResource =
|
||||
rule.destinationResource?.id === peer?.id;
|
||||
const isPeerInDestinationGroup =
|
||||
rule.destinations?.some((group) => {
|
||||
const groupId = typeof group === "string" ? group : group?.id;
|
||||
return peerGroupIds?.includes(groupId);
|
||||
}) ?? false;
|
||||
|
||||
const isPeerInDestination =
|
||||
isPeerInDestinationResource || isPeerInDestinationGroup;
|
||||
|
||||
// If bidirectional, also check if peer is in source
|
||||
let isPeerInSource = false;
|
||||
if (rule.bidirectional) {
|
||||
const isPeerInSourceResource = rule.sourceResource?.id === peer?.id;
|
||||
const isPeerInSourceGroup =
|
||||
rule.sources?.some((group) => {
|
||||
const groupId = typeof group === "string" ? group : group?.id;
|
||||
return peerGroupIds?.includes(groupId);
|
||||
}) ?? false;
|
||||
|
||||
isPeerInSource = isPeerInSourceResource || isPeerInSourceGroup;
|
||||
}
|
||||
|
||||
const isInSourceOrDestination = isPeerInDestination || isPeerInSource;
|
||||
if (!isInSourceOrDestination) return false;
|
||||
|
||||
if (rule.protocol === "all") return true;
|
||||
|
||||
// Check ports
|
||||
const hasNoPortRestrictions = rule.ports === undefined;
|
||||
const hasExplicitPort22 = rule.ports?.includes("22");
|
||||
const hasPort22InRange = rule.port_ranges?.some(
|
||||
(range) => 22 >= range.start && 22 <= range.end,
|
||||
);
|
||||
|
||||
return hasNoPortRestrictions || hasExplicitPort22 || hasPort22InRange;
|
||||
});
|
||||
|
||||
const hasSSHPolicy = (peerPolicies?.length ?? 0) > 0;
|
||||
const showSSHPolicyInfo =
|
||||
!hasSSHPolicy &&
|
||||
!isLoading &&
|
||||
!!peer?.ssh_enabled &&
|
||||
isNativeSSHSupported(peer.version);
|
||||
|
||||
return {
|
||||
peerPolicies,
|
||||
isCheckLoading: isLoading,
|
||||
hasSSHPolicy,
|
||||
showSSHPolicyInfo,
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import FullTooltip from "@components/FullTooltip";
|
||||
import InlineLink from "@components/InlineLink";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -7,14 +8,13 @@ import {
|
||||
} from "@components/Tooltip";
|
||||
import MemoizedNetBirdIcon from "@components/ui/MemoizedNetBirdIcon";
|
||||
import { getOperatingSystem } from "@hooks/useOperatingSystem";
|
||||
import { compareVersions } from "@utils/version";
|
||||
import { parseVersionString } from "@utils/version";
|
||||
import { ArrowRightIcon, ArrowUpCircleIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useMemo } from "react";
|
||||
import { useApplicationContext } from "@/contexts/ApplicationProvider";
|
||||
import { OperatingSystem } from "@/interfaces/OperatingSystem";
|
||||
import { PeerOperatingSystemIcon } from "@/modules/peers/PeerOperatingSystemIcon";
|
||||
import FullTooltip from "@components/FullTooltip";
|
||||
|
||||
type Props = {
|
||||
version: string;
|
||||
@@ -31,8 +31,7 @@ export default function PeerVersionCell({ version, os, serial }: Props) {
|
||||
operatingSystem === OperatingSystem.ANDROID
|
||||
)
|
||||
return false;
|
||||
if (!latestVersion) return false;
|
||||
return !compareVersions(version, latestVersion);
|
||||
return parseVersionString(version) < parseVersionString(latestVersion);
|
||||
}, [os, version, latestVersion]);
|
||||
|
||||
const updateIcon = useMemo(() => {
|
||||
|
||||
@@ -40,16 +40,10 @@ export const useSSH = (client: any) => {
|
||||
setConfig(config);
|
||||
|
||||
try {
|
||||
const requiresJwt = await client.detectSSHServerType(
|
||||
config.hostname,
|
||||
config.port,
|
||||
);
|
||||
|
||||
const ssh = await client.createSSHConnection(
|
||||
config.hostname,
|
||||
config.port,
|
||||
config.username,
|
||||
requiresJwt ? accessToken : undefined,
|
||||
);
|
||||
|
||||
ssh.onclose = () => {
|
||||
@@ -71,7 +65,7 @@ export const useSSH = (client: any) => {
|
||||
return SSHStatus.DISCONNECTED;
|
||||
}
|
||||
},
|
||||
[client, status, accessToken],
|
||||
[client, status],
|
||||
);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ const loadConfig = (): Config => {
|
||||
googleAnalyticsID: configJson?.googleAnalyticsID || undefined,
|
||||
googleTagManagerID: configJson?.googleTagManagerID || undefined,
|
||||
wasmPath:
|
||||
configJson?.wasmPath || "https://pkgs.netbird.io/wasm/client/v0.60.0",
|
||||
configJson.wasmPath || "https://pkgs.netbird.io/wasm/client/v0.60.0",
|
||||
} as Config;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { isNativeSSHSupported } from "./version.js";
|
||||
|
||||
console.log("=== Testing isNativeSSHSupported ===");
|
||||
const sshTestCases = [
|
||||
{ version: "v0.59.9", shouldSupport: false },
|
||||
{ version: "v0.59.10", shouldSupport: false },
|
||||
{ version: "v0.59.11", shouldSupport: false },
|
||||
{ version: "v0.60.0", shouldSupport: true },
|
||||
{ version: "v0.60.1", shouldSupport: true },
|
||||
{ version: "v0.61.0", shouldSupport: true },
|
||||
{ version: "v1.0.0", shouldSupport: true },
|
||||
|
||||
// Edge cases
|
||||
{ version: "development", shouldSupport: true, desc: "development build" },
|
||||
{ version: "0.60.0", shouldSupport: true, desc: "no v prefix" },
|
||||
{ version: "0.59.11", shouldSupport: false, desc: "no v prefix" },
|
||||
{ version: "v0.60.0-beta", shouldSupport: true, desc: "with suffix" },
|
||||
{ version: "v0.60.0-rc1", shouldSupport: true, desc: "with rc suffix" },
|
||||
{ version: "v0.59.9-beta", shouldSupport: false, desc: "old version with suffix" },
|
||||
];
|
||||
|
||||
let failures = 0;
|
||||
sshTestCases.forEach(({ version, shouldSupport, desc }) => {
|
||||
const result = isNativeSSHSupported(version);
|
||||
const status = result === shouldSupport ? "âś“" : "âś—";
|
||||
if (result !== shouldSupport) failures++;
|
||||
const label = desc ? `${version.padEnd(15)} (${desc})` : version.padEnd(15);
|
||||
console.log(
|
||||
`${status} ${label} → ${result.toString().padStart(5)} (expected: ${shouldSupport})`,
|
||||
);
|
||||
});
|
||||
|
||||
console.log(`\n${failures} test(s) failed`);
|
||||
process.exit(failures > 0 ? 1 : 0);
|
||||
+8
-28
@@ -36,30 +36,9 @@ export const getLatestNetbirdRelease = async (
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare semantic versions.
|
||||
* Returns true if version >= minVersion.
|
||||
*/
|
||||
export const compareVersions = (
|
||||
version: string,
|
||||
minVersion: string,
|
||||
): boolean => {
|
||||
const parseVersion = (v: string): number[] => {
|
||||
return v.replace(/^v/, "").split(".").map(Number);
|
||||
};
|
||||
|
||||
const vParts = parseVersion(version);
|
||||
const minParts = parseVersion(minVersion);
|
||||
|
||||
for (let i = 0; i < Math.max(vParts.length, minParts.length); i++) {
|
||||
const vPart = vParts[i] || 0;
|
||||
const minPart = minParts[i] || 0;
|
||||
|
||||
if (vPart > minPart) return true;
|
||||
if (vPart < minPart) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
export const parseVersionString = (version: string | undefined) => {
|
||||
if (!version) return -1;
|
||||
return parseInt(version.replace(/\D/g, ""));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -72,15 +51,16 @@ export const isRoutingPeerSupported = (version: string, os: string) => {
|
||||
const operatingSystem = getOperatingSystem(os);
|
||||
if (operatingSystem == OperatingSystem.LINUX) return true;
|
||||
if (version == "development") return true;
|
||||
return compareVersions(version, "0.36.6");
|
||||
const versionNumber = parseVersionString(version);
|
||||
return versionNumber >= 366;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if native SSH is supported.
|
||||
* Supported starting from NetBird v0.60.0+.
|
||||
* Check if native SSH is supported
|
||||
* @param version
|
||||
*/
|
||||
export const isNativeSSHSupported = (version: string) => {
|
||||
if (version == "development") return true;
|
||||
return compareVersions(version, "0.60.0");
|
||||
const versionNumber = parseVersionString(version);
|
||||
return versionNumber >= 600;
|
||||
};
|
||||
Reference in new issue
Block a user