Compare commits

...

2 Commits

Author SHA1 Message Date
hakansa
5d4e491611 Add skip_auto_apply feature to exit nodes and update related components (#484)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2025-08-20 13:11:45 +02:00
Eduard Gert
9b1f920863 Update dependencies (#483) 2025-08-19 17:32:22 +02:00
11 changed files with 218 additions and 90 deletions

142
package-lock.json generated
View File

@@ -513,9 +513,9 @@
"license": "MIT"
},
"node_modules/@next/env": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.28.tgz",
"integrity": "sha512-PAmWhJfJQlP+kxZwCjrVd9QnR5x0R3u0mTXTiZDgSd4h5LdXmjxCCWbN9kq6hkZBOax8Rm3xDW5HagWyJuT37g==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.32.tgz",
"integrity": "sha512-n9mQdigI6iZ/DF6pCTwMKeWgF2e8lg7qgt5M7HXMLtyhZYMnf/u905M18sSpPmHL9MKp9JHo56C6jrD2EvWxng==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
@@ -529,9 +529,9 @@
}
},
"node_modules/@next/eslint-plugin-next/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -578,9 +578,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.28.tgz",
"integrity": "sha512-kzGChl9setxYWpk3H6fTZXXPFFjg7urptLq5o5ZgYezCrqlemKttwMT5iFyx/p1e/JeglTwDFRtb923gTJ3R1w==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.32.tgz",
"integrity": "sha512-osHXveM70zC+ilfuFa/2W6a1XQxJTvEhzEycnjUaVE8kpUS09lDpiDDX2YLdyFCzoUbvbo5r0X1Kp4MllIOShw==",
"cpu": [
"arm64"
],
@@ -594,9 +594,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.28.tgz",
"integrity": "sha512-z6FXYHDJlFOzVEOiiJ/4NG8aLCeayZdcRSMjPDysW297Up6r22xw6Ea9AOwQqbNsth8JNgIK8EkWz2IDwaLQcw==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.32.tgz",
"integrity": "sha512-P9NpCAJuOiaHHpqtrCNncjqtSBi1f6QUdHK/+dNabBIXB2RUFWL19TY1Hkhu74OvyNQEYEzzMJCMQk5agjw1Qg==",
"cpu": [
"x64"
],
@@ -610,9 +610,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.28.tgz",
"integrity": "sha512-9ARHLEQXhAilNJ7rgQX8xs9aH3yJSj888ssSjJLeldiZKR4D7N08MfMqljk77fAwZsWwsrp8ohHsMvurvv9liQ==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.32.tgz",
"integrity": "sha512-v7JaO0oXXt6d+cFjrrKqYnR2ubrD+JYP7nQVRZgeo5uNE5hkCpWnHmXm9vy3g6foMO8SPwL0P3MPw1c+BjbAzA==",
"cpu": [
"arm64"
],
@@ -626,9 +626,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.28.tgz",
"integrity": "sha512-p6gvatI1nX41KCizEe6JkF0FS/cEEF0u23vKDpl+WhPe/fCTBeGkEBh7iW2cUM0rvquPVwPWdiUR6Ebr/kQWxQ==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.32.tgz",
"integrity": "sha512-tA6sIKShXtSJBTH88i0DRd6I9n3ZTirmwpwAqH5zdJoQF7/wlJXR8DkPmKwYl5mFWhEKr5IIa3LfpMW9RRwKmQ==",
"cpu": [
"arm64"
],
@@ -642,9 +642,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.28.tgz",
"integrity": "sha512-nsiSnz2wO6GwMAX2o0iucONlVL7dNgKUqt/mDTATGO2NY59EO/ZKnKEr80BJFhuA5UC1KZOMblJHWZoqIJddpA==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.32.tgz",
"integrity": "sha512-7S1GY4TdnlGVIdeXXKQdDkfDysoIVFMD0lJuVVMeb3eoVjrknQ0JNN7wFlhCvea0hEk0Sd4D1hedVChDKfV2jw==",
"cpu": [
"x64"
],
@@ -658,9 +658,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.28.tgz",
"integrity": "sha512-+IuGQKoI3abrXFqx7GtlvNOpeExUH1mTIqCrh1LGFf8DnlUcTmOOCApEnPJUSLrSbzOdsF2ho2KhnQoO0I1RDw==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.32.tgz",
"integrity": "sha512-OHHC81P4tirVa6Awk6eCQ6RBfWl8HpFsZtfEkMpJ5GjPsJ3nhPe6wKAJUZ/piC8sszUkAgv3fLflgzPStIwfWg==",
"cpu": [
"x64"
],
@@ -674,9 +674,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.28.tgz",
"integrity": "sha512-l61WZ3nevt4BAnGksUVFKy2uJP5DPz2E0Ma/Oklvo3sGj9sw3q7vBWONFRgz+ICiHpW5mV+mBrkB3XEubMrKaA==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.32.tgz",
"integrity": "sha512-rORQjXsAFeX6TLYJrCG5yoIDj+NKq31Rqwn8Wpn/bkPNy5rTHvOXkW8mLFonItS7QC6M+1JIIcLe+vOCTOYpvg==",
"cpu": [
"arm64"
],
@@ -690,9 +690,9 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.28.tgz",
"integrity": "sha512-+Kcp1T3jHZnJ9v9VTJ/yf1t/xmtFAc/Sge4v7mVc1z+NYfYzisi8kJ9AsY8itbgq+WgEwMtOpiLLJsUy2qnXZw==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.32.tgz",
"integrity": "sha512-jHUeDPVHrgFltqoAqDB6g6OStNnFxnc7Aks3p0KE0FbwAvRg6qWKYF5mSTdCTxA3axoSAUwxYdILzXJfUwlHhA==",
"cpu": [
"ia32"
],
@@ -706,9 +706,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.28.tgz",
"integrity": "sha512-1gCmpvyhz7DkB1srRItJTnmR2UwQPAUXXIg9r0/56g3O8etGmwlX68skKXJOp9EejW3hhv7nSQUJ2raFiz4MoA==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.32.tgz",
"integrity": "sha512-2N0lSoU4GjfLSO50wvKpMQgKd4HdI2UHEhQPPPnlgfBJlOgJxkjpkYBqzk08f1gItBB6xF/n+ykso2hgxuydsA==",
"cpu": [
"x64"
],
@@ -2809,9 +2809,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3396,9 +3396,10 @@
"dev": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -4532,14 +4533,16 @@
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
"integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.2",
"has-tostringtag": "^1.0.0",
"hasown": "^2.0.0"
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -5310,14 +5313,16 @@
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -5683,12 +5688,13 @@
}
},
"node_modules/has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.2"
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
@@ -6783,12 +6789,12 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
},
"node_modules/next": {
"version": "14.2.28",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.28.tgz",
"integrity": "sha512-QLEIP/kYXynIxtcKB6vNjtWLVs3Y4Sb+EClTC/CSVzdLD1gIuItccpu/n1lhmduffI32iPGEK2cLLxxt28qgYA==",
"version": "14.2.32",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.32.tgz",
"integrity": "sha512-fg5g0GZ7/nFc09X8wLe6pNSU8cLWbLRG3TZzPJ1BJvi2s9m7eF991se67wliM9kR5yLHRkyGKU49MMx58s3LJg==",
"license": "MIT",
"dependencies": {
"@next/env": "14.2.28",
"@next/env": "14.2.32",
"@swc/helpers": "0.5.5",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
@@ -6803,15 +6809,15 @@
"node": ">=18.17.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "14.2.28",
"@next/swc-darwin-x64": "14.2.28",
"@next/swc-linux-arm64-gnu": "14.2.28",
"@next/swc-linux-arm64-musl": "14.2.28",
"@next/swc-linux-x64-gnu": "14.2.28",
"@next/swc-linux-x64-musl": "14.2.28",
"@next/swc-win32-arm64-msvc": "14.2.28",
"@next/swc-win32-ia32-msvc": "14.2.28",
"@next/swc-win32-x64-msvc": "14.2.28"
"@next/swc-darwin-arm64": "14.2.32",
"@next/swc-darwin-x64": "14.2.32",
"@next/swc-linux-arm64-gnu": "14.2.32",
"@next/swc-linux-arm64-musl": "14.2.32",
"@next/swc-linux-x64-gnu": "14.2.32",
"@next/swc-linux-x64-musl": "14.2.32",
"@next/swc-win32-arm64-msvc": "14.2.32",
"@next/swc-win32-ia32-msvc": "14.2.32",
"@next/swc-win32-x64-msvc": "14.2.32"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
@@ -8565,9 +8571,9 @@
"license": "MIT"
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -61,6 +61,7 @@ export default function RoutesProvider({ children }: Readonly<Props>) {
: toUpdate.access_control_groups ??
route.access_control_groups ??
undefined,
skip_auto_apply: toUpdate.skip_auto_apply ?? route.skip_auto_apply ?? true,
},
`/${route.id}`,
)
@@ -94,6 +95,7 @@ export default function RoutesProvider({ children }: Readonly<Props>) {
masquerade: route.masquerade,
groups: route.groups || [],
access_control_groups: route?.access_control_groups || undefined,
skip_auto_apply: route.skip_auto_apply ?? true,
})
.then((route) => {
mutate("/routes");

View File

@@ -12,6 +12,7 @@ export interface Route {
groups: string[];
keep_route?: boolean;
access_control_groups?: string[];
skip_auto_apply?: boolean;
// Frontend only
peer_groups?: string[];
routesGroups?: string[];

View File

@@ -15,7 +15,7 @@ type Props = {
export const ExitNodeDropdownButton = ({ peer }: Props) => {
const [modal, setModal] = useState(false);
const hasExitNodes = useHasExitNodes(peer);
const exitNodeInfo = useHasExitNodes(peer);
const { permission } = usePermissions();
return (
@@ -25,7 +25,7 @@ export const ExitNodeDropdownButton = ({ peer }: Props) => {
disabled={!permission.routes.create}
>
<div className={"flex gap-3 items-center w-full"}>
{hasExitNodes ? (
{exitNodeInfo.hasExitNode ? (
<>
<IconCirclePlus size={14} className={"shrink-0"} />
<div className={"flex justify-between items-center w-full"}>

View File

@@ -8,18 +8,22 @@ type Props = {
peer: Peer;
};
export const ExitNodePeerIndicator = ({ peer }: Props) => {
const hasExitNode = useHasExitNodes(peer);
const exitNodeInfo = useHasExitNodes(peer);
return hasExitNode ? (
<FullTooltip
content={
<div className={"text-xs max-w-xs"}>
This peer is an exit node. Traffic from the configured distribution
groups will be routed through this peer.
</div>
}
>
<IconDirectionSign size={15} className={"text-yellow-400 shrink-0"} />
if (!exitNodeInfo.hasExitNode) {
return null;
}
const tooltipContent = exitNodeInfo.skipAutoApply === false
? "This peer is an auto-applied exit node. Traffic from the configured distribution groups will be routed through this peer."
: "This peer is an exit node. Traffic from the configured distribution groups will be routed through this peer.";
return (
<FullTooltip content={<div className={"text-xs max-w-xs"}>{tooltipContent}</div>}>
<IconDirectionSign
size={15}
className={`shrink-0 ${exitNodeInfo.skipAutoApply === false ? "text-green-400" : "text-yellow-400"}`}
/>
</FullTooltip>
) : null;
);
};

View File

@@ -3,7 +3,12 @@ import { useLoggedInUser } from "@/contexts/UsersProvider";
import { Peer } from "@/interfaces/Peer";
import { Route } from "@/interfaces/Route";
export const useHasExitNodes = (peer?: Peer) => {
export interface ExitNodeInfo {
hasExitNode: boolean;
skipAutoApply?: boolean;
}
export const useHasExitNodes = (peer?: Peer): ExitNodeInfo => {
const { isOwnerOrAdmin } = useLoggedInUser();
const { data: routes } = useFetchApi<Route[]>(
`/routes`,
@@ -11,9 +16,17 @@ export const useHasExitNodes = (peer?: Peer) => {
true,
isOwnerOrAdmin,
);
return peer
? routes?.some(
(route) => route?.peer === peer.id && route?.network === "0.0.0.0/0",
) || false
: false;
if (!peer || !routes) {
return { hasExitNode: false };
}
const exitNodeRoute = routes.find(
(route) => route?.peer === peer.id && route?.network === "0.0.0.0/0",
);
return {
hasExitNode: !!exitNodeRoute,
skipAutoApply: exitNodeRoute?.skip_auto_apply,
};
};

View File

@@ -17,7 +17,7 @@ type Props = {
export const PeerNetworkRoutesSection = ({ peer }: Props) => {
const { peerRoutes, isLoading } = usePeerRoutes({ peer });
const hasExitNodes = useHasExitNodes(peer);
const exitNodeInfo = useHasExitNodes(peer);
const { ref: headingRef, portalTarget } =
usePortalElement<HTMLHeadingElement>();
@@ -34,7 +34,7 @@ export const PeerNetworkRoutesSection = ({ peer }: Props) => {
</div>
<div className={"inline-flex gap-4 justify-end"}>
<div className={"gap-4 flex"}>
<AddExitNodeButton peer={peer} firstTime={!hasExitNodes} />
<AddExitNodeButton peer={peer} firstTime={!exitNodeInfo.hasExitNode} />
<AddRouteDropdownButton />
</div>
</div>

View File

@@ -0,0 +1,53 @@
import { ToggleSwitch } from "@components/ToggleSwitch";
import React, { useMemo } from "react";
import { useSWRConfig } from "swr";
import { usePermissions } from "@/contexts/PermissionsProvider";
import { useRoutes } from "@/contexts/RoutesProvider";
import { Route } from "@/interfaces/Route";
type Props = {
route: Route;
};
export default function RouteAutoApplyCell({ route }: Readonly<Props>) {
const { permission } = usePermissions();
const { updateRoute } = useRoutes();
const { mutate } = useSWRConfig();
const isExitNode = useMemo(() => route.network === "0.0.0.0/0", [route]);
const isChecked = useMemo(() => {
// Checked means Auto Apply is ON, which maps to skip_auto_apply === false
return route.skip_auto_apply === false;
}, [route]);
const update = async (checked: boolean) => {
// When toggled ON (checked === true), we want skip_auto_apply = false
const nextSkipAutoApply = !checked;
updateRoute(
route,
{ skip_auto_apply: nextSkipAutoApply },
() => {
mutate("/routes");
},
checked
? "Auto Apply was enabled for the route"
: "Auto Apply was disabled for the route",
);
};
if (!isExitNode) return null;
return (
<div className={"flex items-center"}>
<ToggleSwitch
checked={isChecked}
size={"small"}
onClick={() => update(!isChecked)}
disabled={!permission.routes.update}
/>
</div>
);
}

View File

@@ -227,6 +227,7 @@ export function RouteModalContent({
const [enabled, setEnabled] = useState<boolean>(true);
const [metric, setMetric] = useState("9999");
const [masquerade, setMasquerade] = useState<boolean>(true);
const [isForced, setIsForced] = useState<boolean>(true);
const isNonLinuxRoutingPeer = useMemo(() => {
if (!routingPeer) return false;
@@ -304,6 +305,7 @@ export function RouteModalContent({
masquerade: useSinglePeer && isNonLinuxRoutingPeer ? true : masquerade,
groups: groupIds,
access_control_groups: accessControlGroupIds || undefined,
skip_auto_apply: !isForced,
},
onSuccess,
);
@@ -718,6 +720,20 @@ export function RouteModalContent({
helpText={"Use this switch to enable or disable the route."}
/>
{exitNode && (
<FancyToggleSwitch
value={isForced}
onChange={setIsForced}
label={
<>
<IconDirectionSign size={15} />
Auto Apply Route
</>
}
helpText={"Automatically apply this exit node to your distribution groups. This requires NetBird client v0.55.0 or higher."}
/>
)}
{!exitNode && (
<RoutingPeerMasqueradeSwitch
value={masquerade}

View File

@@ -7,6 +7,7 @@ import { GroupedRoute, Route } from "@/interfaces/Route";
import RouteAccessControlGroups from "@/modules/routes/RouteAccessControlGroups";
import RouteActionCell from "@/modules/routes/RouteActionCell";
import RouteActiveCell from "@/modules/routes/RouteActiveCell";
import RouteAutoApplyCell from "@/modules/routes/RouteAutoApplyCell";
import RouteDistributionGroupsCell from "@/modules/routes/RouteDistributionGroupsCell";
import RouteMetricCell from "@/modules/routes/RouteMetricCell";
import RoutePeerCell from "@/modules/routes/RoutePeerCell";
@@ -77,6 +78,15 @@ export const RouteTableColumns: ColumnDef<Route>[] = [
},
cell: ({ row }) => <RouteAccessControlGroups route={row.original} />,
},
{
id: "skipAutoApply",
accessorKey: "skip_auto_apply",
header: ({ column }) => {
return <DataTableHeader column={column}>Auto Apply</DataTableHeader>;
},
cell: ({ row }) => <RouteAutoApplyCell route={row.original} />,
sortingFn: "basic",
},
{
id: "group_names",
accessorFn: (row) => {
@@ -104,6 +114,10 @@ export default function RouteTable({ row }: Props) {
desc: true,
},
]);
const hasAtLeastOneExitNode = useMemo(() => {
return row.routes?.some((route) => route.network === "0.0.0.0/0");
}, [row.routes]);
const data = useMemo(() => {
if (!row.routes) return [];
@@ -144,6 +158,7 @@ export default function RouteTable({ row }: Props) {
domains: false,
domain_search: false,
network: false,
skipAutoApply: !!hasAtLeastOneExitNode,
}}
setSorting={setSorting}
columns={RouteTableColumns}

View File

@@ -20,6 +20,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@components/Tabs";
import { Textarea } from "@components/Textarea";
import { DomainsTooltip } from "@components/ui/DomainListBadge";
import { getOperatingSystem } from "@hooks/useOperatingSystem";
import { IconDirectionSign } from "@tabler/icons-react";
import { cn } from "@utils/helpers";
import { uniqBy } from "lodash";
import {
@@ -193,6 +194,7 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) {
const [masquerade, setMasquerade] = useState<boolean>(
route?.masquerade ?? true,
);
const [isForced, setIsForced] = useState<boolean>(route?.skip_auto_apply === false);
// Refs to manage focus on tab change
const networkRangeRef = useRef<HTMLInputElement>(null);
@@ -257,6 +259,7 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) {
masquerade: useSinglePeer && isNonLinuxRoutingPeer ? true : masquerade,
groups: groupIds,
access_control_groups: accessControlGroupIds || undefined,
skip_auto_apply: !isForced,
},
(r) => {
onSuccess && onSuccess(r);
@@ -456,6 +459,21 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) {
}
helpText={"Use this switch to enable or disable the route."}
/>
{isExitNode && (
<FancyToggleSwitch
value={isForced}
onChange={setIsForced}
label={
<>
<IconDirectionSign size={15} />
Auto Apply Route
</>
}
helpText={"Automatically apply this exit node to your distribution groups. This requires NetBird client v0.55.0 or higher."}
/>
)}
{!isExitNode && (
<RoutingPeerMasqueradeSwitch
value={masquerade}