mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8c6d44452 |
No files matched your search
Generated
+10
-19
@@ -58,6 +58,7 @@
|
|||||||
"ip-address": "^10.1.0",
|
"ip-address": "^10.1.0",
|
||||||
"ip-cidr": "^3.1.0",
|
"ip-cidr": "^3.1.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"js-yaml": "^4.1.1",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"lucide-react": "^0.566.0",
|
"lucide-react": "^0.566.0",
|
||||||
"next": "16.1.7",
|
"next": "16.1.7",
|
||||||
@@ -85,6 +86,7 @@
|
|||||||
"@faker-js/faker": "^9.5.1",
|
"@faker-js/faker": "^9.5.1",
|
||||||
"@types/chroma-js": "^3.1.1",
|
"@types/chroma-js": "^3.1.1",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-next": "^16.1.6",
|
"eslint-config-next": "^16.1.6",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
@@ -168,7 +170,6 @@
|
|||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -2998,6 +2999,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/js-yaml": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -3031,7 +3039,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
|
||||||
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -3041,7 +3048,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
@@ -3100,7 +3106,6 @@
|
|||||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.54.0",
|
"@typescript-eslint/scope-manager": "8.54.0",
|
||||||
"@typescript-eslint/types": "8.54.0",
|
"@typescript-eslint/types": "8.54.0",
|
||||||
@@ -3581,8 +3586,7 @@
|
|||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@xyflow/react": {
|
"node_modules/@xyflow/react": {
|
||||||
"version": "12.10.0",
|
"version": "12.10.0",
|
||||||
@@ -3621,7 +3625,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -4044,7 +4047,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -4667,7 +4669,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@@ -5196,7 +5197,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -5394,7 +5394,6 @@
|
|||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -6688,7 +6687,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
@@ -7446,7 +7444,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -7654,7 +7651,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -7695,7 +7691,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -8590,7 +8585,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
|
||||||
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
|
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
@@ -8757,7 +8751,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -8923,7 +8916,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -9251,7 +9243,6 @@
|
|||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"ip-address": "^10.1.0",
|
"ip-address": "^10.1.0",
|
||||||
"ip-cidr": "^3.1.0",
|
"ip-cidr": "^3.1.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"js-yaml": "^4.1.1",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"lucide-react": "^0.566.0",
|
"lucide-react": "^0.566.0",
|
||||||
"next": "16.1.7",
|
"next": "16.1.7",
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
"@faker-js/faker": "^9.5.1",
|
"@faker-js/faker": "^9.5.1",
|
||||||
"@types/chroma-js": "^3.1.1",
|
"@types/chroma-js": "^3.1.1",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-next": "^16.1.6",
|
"eslint-config-next": "^16.1.6",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { globalMetaTitle } from "@utils/meta";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import BlankLayout from "@/layouts/BlankLayout";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: `Inspection Policies - ${globalMetaTitle}`,
|
||||||
|
};
|
||||||
|
export default BlankLayout;
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Breadcrumbs from "@components/Breadcrumbs";
|
||||||
|
import InlineLink from "@components/InlineLink";
|
||||||
|
import Paragraph from "@components/Paragraph";
|
||||||
|
import SkeletonTable from "@components/skeletons/SkeletonTable";
|
||||||
|
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
|
||||||
|
import useFetchApi from "@utils/api";
|
||||||
|
import { ExternalLinkIcon, ShieldCheck } from "lucide-react";
|
||||||
|
import React, { lazy, Suspense } from "react";
|
||||||
|
import AccessControlIcon from "@/assets/icons/AccessControlIcon";
|
||||||
|
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||||
|
import PoliciesProvider from "@/contexts/PoliciesProvider";
|
||||||
|
import { InspectionPolicy } from "@/interfaces/Network";
|
||||||
|
import PageContainer from "@/layouts/PageContainer";
|
||||||
|
|
||||||
|
const InspectionPoliciesTable = lazy(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
"@/modules/networks/inspection/InspectionPoliciesTable"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function InspectionPoliciesPage() {
|
||||||
|
const { permission } = usePermissions();
|
||||||
|
const { data: policies, isLoading } =
|
||||||
|
useFetchApi<InspectionPolicy[]>("/inspection-policies");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<div className={"p-default py-6"}>
|
||||||
|
<Breadcrumbs>
|
||||||
|
<Breadcrumbs.Item
|
||||||
|
href={"/access-control"}
|
||||||
|
label={"Access Control"}
|
||||||
|
icon={<AccessControlIcon size={14} />}
|
||||||
|
/>
|
||||||
|
<Breadcrumbs.Item
|
||||||
|
href={"/access-control/inspection-policies"}
|
||||||
|
label={"Inspection Policies"}
|
||||||
|
icon={<ShieldCheck size={15} />}
|
||||||
|
/>
|
||||||
|
</Breadcrumbs>
|
||||||
|
<h1>Inspection Policies</h1>
|
||||||
|
<Paragraph>
|
||||||
|
Create reusable inspection rule sets for transparent proxy traffic
|
||||||
|
inspection. Attach them to access control policies to inspect traffic
|
||||||
|
flowing through routing peers.
|
||||||
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
<PoliciesProvider>
|
||||||
|
<Suspense fallback={<SkeletonTable />}>
|
||||||
|
<InspectionPoliciesTable
|
||||||
|
policies={policies ?? []}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</PoliciesProvider>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import Breadcrumbs from "@components/Breadcrumbs";
|
import Breadcrumbs from "@components/Breadcrumbs";
|
||||||
import Button from "@components/Button";
|
import Button from "@components/Button";
|
||||||
import { Callout } from "@components/Callout";
|
|
||||||
import Card from "@components/Card";
|
import Card from "@components/Card";
|
||||||
import HelpText from "@components/HelpText";
|
import HelpText from "@components/HelpText";
|
||||||
import { Input } from "@components/Input";
|
import { Input } from "@components/Input";
|
||||||
@@ -72,6 +71,7 @@ import ReverseProxiesProvider, {
|
|||||||
useReverseProxies,
|
useReverseProxies,
|
||||||
} from "@/contexts/ReverseProxiesProvider";
|
} from "@/contexts/ReverseProxiesProvider";
|
||||||
import { ReverseProxyFlatTargetsTabContent } from "@/modules/reverse-proxy/targets/flat/ReverseProxyFlatTargetsTabContent";
|
import { ReverseProxyFlatTargetsTabContent } from "@/modules/reverse-proxy/targets/flat/ReverseProxyFlatTargetsTabContent";
|
||||||
|
import { PeerEditIPModal } from "@/modules/peer/PeerEditIPModal";
|
||||||
import { PeerSSHToggle } from "@/modules/peer/PeerSSHToggle";
|
import { PeerSSHToggle } from "@/modules/peer/PeerSSHToggle";
|
||||||
import { RDPButton } from "@/modules/remote-access/rdp/RDPButton";
|
import { RDPButton } from "@/modules/remote-access/rdp/RDPButton";
|
||||||
import { SSHButton } from "@/modules/remote-access/ssh/SSHButton";
|
import { SSHButton } from "@/modules/remote-access/ssh/SSHButton";
|
||||||
@@ -469,31 +469,55 @@ function PeerInformationCard({ peer }: Readonly<{ peer: Peer }>) {
|
|||||||
const { update } = usePeer();
|
const { update } = usePeer();
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
const [showEditIPModal, setShowEditIPModal] = useState(false);
|
const [showEditIPModal, setShowEditIPModal] = useState(false);
|
||||||
|
const [showEditIPv6Modal, setShowEditIPv6Modal] = useState(false);
|
||||||
const { permission } = usePermissions();
|
const { permission } = usePermissions();
|
||||||
|
|
||||||
const countryText = useMemo(() => {
|
const countryText = useMemo(() => {
|
||||||
return getRegionByPeer(peer);
|
return getRegionByPeer(peer);
|
||||||
}, [getRegionByPeer, peer]);
|
}, [getRegionByPeer, peer]);
|
||||||
|
|
||||||
|
const handleSaveIP = (newIP: string) => {
|
||||||
|
notify({
|
||||||
|
title: peer.name,
|
||||||
|
description: "NetBird Peer IP was successfully updated",
|
||||||
|
promise: update({ ip: newIP }).then(() => {
|
||||||
|
mutate("/peers/" + peer.id);
|
||||||
|
setShowEditIPModal(false);
|
||||||
|
}),
|
||||||
|
loadingMessage: "Updating peer IP...",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveIPv6 = (newIPv6: string) => {
|
||||||
|
notify({
|
||||||
|
title: peer.name,
|
||||||
|
description: "NetBird Peer IPv6 was successfully updated",
|
||||||
|
promise: update({ ipv6: newIPv6 }).then(() => {
|
||||||
|
mutate("/peers/" + peer.id);
|
||||||
|
setShowEditIPv6Modal(false);
|
||||||
|
}),
|
||||||
|
loadingMessage: "Updating peer IPv6...",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal open={showEditIPModal} onOpenChange={setShowEditIPModal}>
|
<PeerEditIPModal
|
||||||
<EditIPModal
|
version="v4"
|
||||||
onSuccess={(newIP) => {
|
currentIP={peer.ip}
|
||||||
notify({
|
open={showEditIPModal}
|
||||||
title: peer.name,
|
onOpenChange={setShowEditIPModal}
|
||||||
description: "Peer IP was successfully updated",
|
onSave={handleSaveIP}
|
||||||
promise: update({ ip: newIP }).then(() => {
|
key={showEditIPModal ? "v4-open" : "v4-closed"}
|
||||||
mutate("/peers/" + peer.id);
|
/>
|
||||||
setShowEditIPModal(false);
|
<PeerEditIPModal
|
||||||
}),
|
version="v6"
|
||||||
loadingMessage: "Updating peer IP...",
|
currentIP={peer.ipv6 || ""}
|
||||||
});
|
open={showEditIPv6Modal}
|
||||||
}}
|
onOpenChange={setShowEditIPv6Modal}
|
||||||
peer={peer}
|
onSave={handleSaveIPv6}
|
||||||
key={showEditIPModal ? 1 : 0}
|
key={showEditIPv6Modal ? "v6-open" : "v6-closed"}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
|
||||||
<Card className={"w-full xl:w-1/2"}>
|
<Card className={"w-full xl:w-1/2"}>
|
||||||
<Card.List>
|
<Card.List>
|
||||||
<Card.ListItem
|
<Card.ListItem
|
||||||
@@ -508,23 +532,36 @@ function PeerInformationCard({ peer }: Readonly<{ peer: Peer }>) {
|
|||||||
}
|
}
|
||||||
valueToCopy={peer.ip}
|
valueToCopy={peer.ip}
|
||||||
value={
|
value={
|
||||||
<div className="flex items-center gap-2 justify-between w-full">
|
<EditableValue
|
||||||
<span>{peer.ip}</span>
|
value={peer.ip}
|
||||||
{permission.peers.update && (
|
canEdit={permission.peers.update}
|
||||||
<button
|
onEdit={() => setShowEditIPModal(true)}
|
||||||
className="flex w-7 h-7 items-center justify-center gap-2 text-nb-gray-400 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer"
|
/>
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setShowEditIPModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PencilIcon size={14} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{peer.ipv6 && (
|
||||||
|
<Card.ListItem
|
||||||
|
copy
|
||||||
|
tooltip={false}
|
||||||
|
copyText={"NetBird IPv6 Address"}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<MapPin size={16} />
|
||||||
|
NetBird IPv6 Address
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
valueToCopy={peer.ipv6}
|
||||||
|
value={
|
||||||
|
<EditableValue
|
||||||
|
value={peer.ipv6}
|
||||||
|
canEdit={permission.peers.update}
|
||||||
|
onEdit={() => setShowEditIPv6Modal(true)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Card.ListItem
|
<Card.ListItem
|
||||||
copy
|
copy
|
||||||
copyText={"Public IP Address"}
|
copyText={"Public IP Address"}
|
||||||
@@ -765,82 +802,29 @@ function EditNameModal({ onSuccess, peer, initialName }: Readonly<ModalProps>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EditIPModalProps {
|
function EditableValue({
|
||||||
onSuccess: (ip: string) => void;
|
value,
|
||||||
peer: Peer;
|
canEdit,
|
||||||
}
|
onEdit,
|
||||||
|
}: {
|
||||||
function EditIPModal({ onSuccess, peer }: Readonly<EditIPModalProps>) {
|
value: string;
|
||||||
const [ip, setIP] = useState(peer.ip);
|
canEdit: boolean;
|
||||||
const [error, setError] = useState("");
|
onEdit: () => void;
|
||||||
|
}) {
|
||||||
const validateIP = (ipAddress: string) => {
|
|
||||||
const ipRegex =
|
|
||||||
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
||||||
return ipRegex.test(ipAddress);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDisabled = useMemo(() => {
|
|
||||||
if (ip === peer.ip) return true;
|
|
||||||
const trimmedIP = trim(ip);
|
|
||||||
return trimmedIP.length === 0 || !validateIP(ip);
|
|
||||||
}, [ip, peer.ip]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
switch (true) {
|
|
||||||
case ip === peer.ip:
|
|
||||||
setError("");
|
|
||||||
break;
|
|
||||||
case !validateIP(ip):
|
|
||||||
setError("Please enter a valid IP, e.g., 100.64.0.15");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setError("");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}, [ip, peer.ip]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent maxWidthClass={"max-w-md"}>
|
<div className="flex items-center gap-2 justify-between w-full">
|
||||||
<form>
|
<span>{value}</span>
|
||||||
<ModalHeader
|
{canEdit && (
|
||||||
title={"Edit Peer IP Address"}
|
<button
|
||||||
description={"Update the NetBird IP address for this peer."}
|
className="flex w-7 h-7 items-center justify-center gap-2 text-nb-gray-400 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer"
|
||||||
color={"blue"}
|
onClick={(e) => {
|
||||||
/>
|
e.stopPropagation();
|
||||||
|
onEdit();
|
||||||
<div className={"p-default flex flex-col gap-4"}>
|
}}
|
||||||
<div>
|
>
|
||||||
<Input
|
<PencilIcon size={14} />
|
||||||
placeholder={"e.g., 100.64.0.15"}
|
</button>
|
||||||
value={ip}
|
)}
|
||||||
onChange={(e) => setIP(e.target.value)}
|
</div>
|
||||||
error={error}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Callout>Changes take effect when the peer reconnects.</Callout>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ModalFooter className={"items-center"} separator={false}>
|
|
||||||
<div className={"flex gap-3 w-full justify-end"}>
|
|
||||||
<ModalClose asChild={true}>
|
|
||||||
<Button variant={"secondary"} className={"w-full"}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</ModalClose>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant={"primary"}
|
|
||||||
className={"w-full"}
|
|
||||||
onClick={() => onSuccess(ip)}
|
|
||||||
disabled={isDisabled}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</ModalFooter>
|
|
||||||
</form>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
} from "@utils/version";
|
} from "@utils/version";
|
||||||
|
|
||||||
export default function SSHPage() {
|
export default function SSHPage() {
|
||||||
const { peerId, username, port } = useSSHQueryParams();
|
const { peerId, username, port, ipVersion } = useSSHQueryParams();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: peer,
|
data: peer,
|
||||||
@@ -48,6 +48,7 @@ export default function SSHPage() {
|
|||||||
peer={peer}
|
peer={peer}
|
||||||
username={username}
|
username={username}
|
||||||
port={port}
|
port={port}
|
||||||
|
ipVersion={ipVersion}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LoadingMessage message={"Starting ssh session..."} />
|
<LoadingMessage message={"Starting ssh session..."} />
|
||||||
@@ -60,9 +61,10 @@ type Props = {
|
|||||||
username: string;
|
username: string;
|
||||||
port: string;
|
port: string;
|
||||||
peer: Peer;
|
peer: Peer;
|
||||||
|
ipVersion: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function SSHTerminal({ username, port, peer }: Props) {
|
function SSHTerminal({ username, port, peer, ipVersion }: Props) {
|
||||||
const client = useNetBirdClient();
|
const client = useNetBirdClient();
|
||||||
const connected = useRef(false);
|
const connected = useRef(false);
|
||||||
const sshConnectedOnce = useRef(false);
|
const sshConnectedOnce = useRef(false);
|
||||||
@@ -81,9 +83,12 @@ function SSHTerminal({ username, port, peer }: Props) {
|
|||||||
const isClientDisconnected = client.status === NetBirdStatus.DISCONNECTED;
|
const isClientDisconnected = client.status === NetBirdStatus.DISCONNECTED;
|
||||||
const isClientConnecting = client.status === NetBirdStatus.CONNECTING;
|
const isClientConnecting = client.status === NetBirdStatus.CONNECTING;
|
||||||
|
|
||||||
|
// Use the FQDN when an IP version is specified so the dialer resolves to the correct address family.
|
||||||
|
const sshHost = ipVersion ? peer.dns_label || peer.ip : peer.ip;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${username}@${peer.ip} - ${peer.hostname}`;
|
document.title = `${username}@${sshHost} - ${peer.hostname}`;
|
||||||
}, [username, peer, client]);
|
}, [username, peer, client, sshHost]);
|
||||||
|
|
||||||
const handleReconnect = async () => {
|
const handleReconnect = async () => {
|
||||||
if (!peer?.id) return;
|
if (!peer?.id) return;
|
||||||
@@ -97,9 +102,10 @@ function SSHTerminal({ username, port, peer }: Props) {
|
|||||||
const rules = [`${protocol}/${aclPort}`];
|
const rules = [`${protocol}/${aclPort}`];
|
||||||
await client?.connectTemporary(peer.id, rules);
|
await client?.connectTemporary(peer.id, rules);
|
||||||
await ssh({
|
await ssh({
|
||||||
hostname: peer.ip,
|
hostname: sshHost,
|
||||||
port: Number(port),
|
port: Number(port),
|
||||||
username,
|
username,
|
||||||
|
ipVersion: ipVersion || undefined,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Reconnection failed:", error);
|
console.error("Reconnection failed:", error);
|
||||||
@@ -123,9 +129,10 @@ function SSHTerminal({ username, port, peer }: Props) {
|
|||||||
const rules = [`${protocol}/${aclPort}`];
|
const rules = [`${protocol}/${aclPort}`];
|
||||||
await client?.connectTemporary(peer.id, rules);
|
await client?.connectTemporary(peer.id, rules);
|
||||||
const res = await ssh({
|
const res = await ssh({
|
||||||
hostname: peer.ip,
|
hostname: sshHost,
|
||||||
port: Number(port),
|
port: Number(port),
|
||||||
username,
|
username,
|
||||||
|
ipVersion: ipVersion || undefined,
|
||||||
});
|
});
|
||||||
if (res === SSHStatus.CONNECTED) {
|
if (res === SSHStatus.CONNECTED) {
|
||||||
sshConnectedOnce.current = true;
|
sshConnectedOnce.current = true;
|
||||||
|
|||||||
@@ -28,8 +28,13 @@ export const DeviceCard = ({
|
|||||||
const descriptionText = useMemo(() => {
|
const descriptionText = useMemo(() => {
|
||||||
return description !== undefined
|
return description !== undefined
|
||||||
? description
|
? description
|
||||||
: address || device?.ip || resource?.address;
|
: address ||
|
||||||
}, [description, address, device]);
|
(device?.ip
|
||||||
|
? device.ipv6
|
||||||
|
? `${device.ip}, ${device.ipv6}`
|
||||||
|
: device.ip
|
||||||
|
: resource?.address);
|
||||||
|
}, [description, address, device, resource]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -968,7 +968,8 @@ const ResourcesList = ({
|
|||||||
const peersSearchPredicate = (item: Peer, query: string) => {
|
const peersSearchPredicate = (item: Peer, query: string) => {
|
||||||
const lowerCaseQuery = query.toLowerCase();
|
const lowerCaseQuery = query.toLowerCase();
|
||||||
if (item.name.toLowerCase().includes(lowerCaseQuery)) return true;
|
if (item.name.toLowerCase().includes(lowerCaseQuery)) return true;
|
||||||
return item.ip.toLowerCase().includes(lowerCaseQuery);
|
if (item.ip.toLowerCase().includes(lowerCaseQuery)) return true;
|
||||||
|
return item.ipv6?.toLowerCase().includes(lowerCaseQuery) ?? false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PeersList = ({
|
const PeersList = ({
|
||||||
@@ -1059,6 +1060,7 @@ const PeersList = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{res.ip}
|
{res.ip}
|
||||||
|
{res.ipv6 && `, ${res.ipv6}`}
|
||||||
<RadioItem value={res.id} />
|
<RadioItem value={res.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ const searchPredicate = (item: Peer, query: string) => {
|
|||||||
const lowerCaseQuery = query.toLowerCase();
|
const lowerCaseQuery = query.toLowerCase();
|
||||||
if (item.name.toLowerCase().includes(lowerCaseQuery)) return true;
|
if (item.name.toLowerCase().includes(lowerCaseQuery)) return true;
|
||||||
if (item.hostname.toLowerCase().includes(lowerCaseQuery)) return true;
|
if (item.hostname.toLowerCase().includes(lowerCaseQuery)) return true;
|
||||||
return item.ip.toLowerCase().startsWith(lowerCaseQuery);
|
if (item.ip.toLowerCase().startsWith(lowerCaseQuery)) return true;
|
||||||
|
return !!item.ipv6?.toLowerCase().startsWith(lowerCaseQuery);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PeerSelector({
|
export function PeerSelector({
|
||||||
@@ -126,6 +127,7 @@ export function PeerSelector({
|
|||||||
>
|
>
|
||||||
<MapPinIcon />
|
<MapPinIcon />
|
||||||
{value.ip}
|
{value.ip}
|
||||||
|
{value.ipv6 && `, ${value.ipv6}`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -240,6 +242,7 @@ export function PeerSelector({
|
|||||||
>
|
>
|
||||||
<MapPinIcon />
|
<MapPinIcon />
|
||||||
{option.ip}
|
{option.ip}
|
||||||
|
{option.ipv6 && `, ${option.ipv6}`}
|
||||||
</div>
|
</div>
|
||||||
</FullTooltip>
|
</FullTooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const PeerContext = React.createContext(
|
|||||||
inactivityExpiration?: boolean;
|
inactivityExpiration?: boolean;
|
||||||
approval_required?: boolean;
|
approval_required?: boolean;
|
||||||
ip?: string;
|
ip?: string;
|
||||||
|
ipv6?: string;
|
||||||
}) => Promise<Peer>;
|
}) => Promise<Peer>;
|
||||||
toggleSSH: (newState: boolean) => Promise<void>;
|
toggleSSH: (newState: boolean) => Promise<void>;
|
||||||
setSSHInstructionsModal: (open: boolean) => void;
|
setSSHInstructionsModal: (open: boolean) => void;
|
||||||
@@ -80,6 +81,7 @@ export default function PeerProvider({
|
|||||||
inactivityExpiration?: boolean;
|
inactivityExpiration?: boolean;
|
||||||
approval_required?: boolean;
|
approval_required?: boolean;
|
||||||
ip?: string;
|
ip?: string;
|
||||||
|
ipv6?: string;
|
||||||
}) => {
|
}) => {
|
||||||
return peerRequest.put(
|
return peerRequest.put(
|
||||||
{
|
{
|
||||||
@@ -99,6 +101,7 @@ export default function PeerProvider({
|
|||||||
? undefined
|
? undefined
|
||||||
: props.approval_required,
|
: props.approval_required,
|
||||||
ip: props.ip != undefined ? props.ip : undefined,
|
ip: props.ip != undefined ? props.ip : undefined,
|
||||||
|
ipv6: props.ipv6 != undefined ? props.ipv6 : undefined,
|
||||||
},
|
},
|
||||||
`/${peer.id}`,
|
`/${peer.id}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ export default function PoliciesProvider({ children }: Props) {
|
|||||||
source_posture_checks: (policy.source_posture_checks ?? []).map((c) =>
|
source_posture_checks: (policy.source_posture_checks ?? []).map((c) =>
|
||||||
typeof c === "string" ? c : c.id,
|
typeof c === "string" ? c : c.id,
|
||||||
),
|
),
|
||||||
|
inspection_policies: (policy.inspection_policies ?? []).map((p) =>
|
||||||
|
typeof p === "string" ? p : p.id,
|
||||||
|
),
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
...rule,
|
...rule,
|
||||||
@@ -154,6 +157,10 @@ export default function PoliciesProvider({ children }: Props) {
|
|||||||
toUpdate.source_posture_checks ??
|
toUpdate.source_posture_checks ??
|
||||||
policy.source_posture_checks ??
|
policy.source_posture_checks ??
|
||||||
[],
|
[],
|
||||||
|
inspection_policies:
|
||||||
|
toUpdate.inspection_policies ??
|
||||||
|
policy.inspection_policies ??
|
||||||
|
[],
|
||||||
},
|
},
|
||||||
`/${policy.id}`,
|
`/${policy.id}`,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export interface Account {
|
|||||||
auto_update_version: string;
|
auto_update_version: string;
|
||||||
auto_update_always: boolean;
|
auto_update_always: boolean;
|
||||||
local_auth_disabled?: boolean;
|
local_auth_disabled?: boolean;
|
||||||
|
ipv6_enabled_groups?: string[];
|
||||||
|
network_range_v6?: string;
|
||||||
};
|
};
|
||||||
onboarding?: AccountOnboarding;
|
onboarding?: AccountOnboarding;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,90 @@ export interface NetworkRouter {
|
|||||||
metric: number;
|
metric: number;
|
||||||
masquerade: boolean;
|
masquerade: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
inspection?: InspectionConfig;
|
||||||
search?: string;
|
search?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InspectionAction = "allow" | "block" | "inspect";
|
||||||
|
export type InspectionMode = "builtin" | "envoy" | "external";
|
||||||
|
|
||||||
|
export interface InspectionEnvoySnippets {
|
||||||
|
http_filters?: string;
|
||||||
|
network_filters?: string;
|
||||||
|
clusters?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InspectionConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
mode?: InspectionMode;
|
||||||
|
external_url?: string;
|
||||||
|
default_action?: InspectionAction;
|
||||||
|
redirect_ports?: number[];
|
||||||
|
rules?: InspectionRule[];
|
||||||
|
icap?: InspectionICAPConfig;
|
||||||
|
ca_cert_pem?: string;
|
||||||
|
ca_key_pem?: string;
|
||||||
|
listen_port?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InspectionRule {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
enabled: boolean;
|
||||||
|
domains?: string[];
|
||||||
|
networks?: string[];
|
||||||
|
ports?: number[];
|
||||||
|
action: InspectionAction;
|
||||||
|
priority: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reusable inspection policy referenced from access control policies.
|
||||||
|
// Contains both L7 rules and proxy infrastructure config (CA, ICAP, mode).
|
||||||
|
export interface InspectionPolicy {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
rules: InspectionPolicyRule[];
|
||||||
|
|
||||||
|
// Proxy infrastructure config
|
||||||
|
mode?: InspectionMode;
|
||||||
|
external_url?: string;
|
||||||
|
default_action?: InspectionAction;
|
||||||
|
redirect_ports?: number[];
|
||||||
|
ca_cert_pem?: string;
|
||||||
|
ca_key_pem?: string;
|
||||||
|
icap?: InspectionICAPConfig;
|
||||||
|
|
||||||
|
// Envoy sidecar config (mode "envoy" only)
|
||||||
|
envoy_binary_path?: string;
|
||||||
|
envoy_admin_port?: number;
|
||||||
|
envoy_snippets?: InspectionEnvoySnippets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InspectionPolicyRule {
|
||||||
|
domains?: string[];
|
||||||
|
networks?: string[];
|
||||||
|
protocols?: InspectionProtocol[];
|
||||||
|
paths?: string[];
|
||||||
|
action: InspectionAction;
|
||||||
|
priority: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InspectionProtocol =
|
||||||
|
| "http"
|
||||||
|
| "https"
|
||||||
|
| "h2"
|
||||||
|
| "h3"
|
||||||
|
| "websocket"
|
||||||
|
| "other";
|
||||||
|
|
||||||
|
export interface InspectionICAPConfig {
|
||||||
|
reqmod_url?: string;
|
||||||
|
respmod_url?: string;
|
||||||
|
max_connections?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NetworkResource {
|
export interface NetworkResource {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface Peer {
|
|||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
|
ipv6?: string;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
created_at?: Date;
|
created_at?: Date;
|
||||||
last_seen: Date;
|
last_seen: Date;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Group } from "@/interfaces/Group";
|
import { Group } from "@/interfaces/Group";
|
||||||
|
import { InspectionPolicy } from "@/interfaces/Network";
|
||||||
import { PostureCheck } from "@/interfaces/PostureCheck";
|
import { PostureCheck } from "@/interfaces/PostureCheck";
|
||||||
|
|
||||||
export interface Policy {
|
export interface Policy {
|
||||||
@@ -9,6 +10,7 @@ export interface Policy {
|
|||||||
query?: string;
|
query?: string;
|
||||||
rules: PolicyRule[];
|
rules: PolicyRule[];
|
||||||
source_posture_checks: string[] | PostureCheck[];
|
source_posture_checks: string[] | PostureCheck[];
|
||||||
|
inspection_policies?: string[] | InspectionPolicy[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PolicyRule {
|
export interface PolicyRule {
|
||||||
|
|||||||
@@ -121,6 +121,13 @@ export default function Navigation({
|
|||||||
href={"/groups"}
|
href={"/groups"}
|
||||||
visible={permission.policies.read}
|
visible={permission.policies.read}
|
||||||
/>
|
/>
|
||||||
|
<SidebarItem
|
||||||
|
label="Inspection Policies"
|
||||||
|
isChild
|
||||||
|
href={"/access-control/inspection-policies"}
|
||||||
|
exactPathMatch={true}
|
||||||
|
visible={permission.policies.read}
|
||||||
|
/>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label="Posture Checks"
|
label="Posture Checks"
|
||||||
isChild
|
isChild
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ import { NetworkResource } from "@/interfaces/Network";
|
|||||||
import { Policy, PolicyRuleResource, Protocol } from "@/interfaces/Policy";
|
import { Policy, PolicyRuleResource, Protocol } from "@/interfaces/Policy";
|
||||||
import { PostureCheck } from "@/interfaces/PostureCheck";
|
import { PostureCheck } from "@/interfaces/PostureCheck";
|
||||||
import { useAccessControl } from "@/modules/access-control/useAccessControl";
|
import { useAccessControl } from "@/modules/access-control/useAccessControl";
|
||||||
|
import { InspectionPolicy } from "@/interfaces/Network";
|
||||||
|
import { InspectionTab } from "@/modules/networks/inspection/InspectionTab";
|
||||||
|
import { InspectionTabTrigger } from "@/modules/networks/inspection/InspectionTabTrigger";
|
||||||
import { PostureCheckTab } from "@/modules/posture-checks/ui/PostureCheckTab";
|
import { PostureCheckTab } from "@/modules/posture-checks/ui/PostureCheckTab";
|
||||||
import { PostureCheckTabTrigger } from "@/modules/posture-checks/ui/PostureCheckTabTrigger";
|
import { PostureCheckTabTrigger } from "@/modules/posture-checks/ui/PostureCheckTabTrigger";
|
||||||
import { SSHAccessType } from "@/modules/access-control/ssh/SSHAccessType";
|
import { SSHAccessType } from "@/modules/access-control/ssh/SSHAccessType";
|
||||||
@@ -186,6 +189,9 @@ export function AccessControlModalContent({
|
|||||||
setSshAccessType,
|
setSshAccessType,
|
||||||
sshAuthorizedGroups,
|
sshAuthorizedGroups,
|
||||||
setSshAuthorizedGroups,
|
setSshAuthorizedGroups,
|
||||||
|
inspectionPolicies,
|
||||||
|
setInspectionPolicies,
|
||||||
|
isInspectionPoliciesLoading,
|
||||||
} = useAccessControl({
|
} = useAccessControl({
|
||||||
policy,
|
policy,
|
||||||
postureCheckTemplates,
|
postureCheckTemplates,
|
||||||
@@ -202,6 +208,7 @@ export function AccessControlModalContent({
|
|||||||
if (initialTab && initialTab !== "") return initialTab;
|
if (initialTab && initialTab !== "") return initialTab;
|
||||||
if (!cell) return "policy";
|
if (!cell) return "policy";
|
||||||
if (cell == "posture_checks") return "posture_checks";
|
if (cell == "posture_checks") return "posture_checks";
|
||||||
|
if (cell == "inspection_policies") return "inspection";
|
||||||
return "policy";
|
return "policy";
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -252,6 +259,7 @@ export function AccessControlModalContent({
|
|||||||
Policy
|
Policy
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<PostureCheckTabTrigger disabled={!canContinueToPostureChecks} />
|
<PostureCheckTabTrigger disabled={!canContinueToPostureChecks} />
|
||||||
|
<InspectionTabTrigger disabled={!canContinueToPostureChecks} />
|
||||||
<TabsTrigger value={"general"} disabled={!canContinueToPostureChecks}>
|
<TabsTrigger value={"general"} disabled={!canContinueToPostureChecks}>
|
||||||
<Text
|
<Text
|
||||||
size={16}
|
size={16}
|
||||||
@@ -518,6 +526,11 @@ export function AccessControlModalContent({
|
|||||||
postureChecks={postureChecks}
|
postureChecks={postureChecks}
|
||||||
setPostureChecks={setPostureChecks}
|
setPostureChecks={setPostureChecks}
|
||||||
/>
|
/>
|
||||||
|
<InspectionTab
|
||||||
|
isLoading={isInspectionPoliciesLoading}
|
||||||
|
inspectionPolicies={inspectionPolicies}
|
||||||
|
setInspectionPolicies={setInspectionPolicies}
|
||||||
|
/>
|
||||||
<TabsContent value={"general"} className={"px-8 pb-6"}>
|
<TabsContent value={"general"} className={"px-8 pb-6"}>
|
||||||
<div className={"flex flex-col gap-6"}>
|
<div className={"flex flex-col gap-6"}>
|
||||||
<div>
|
<div>
|
||||||
@@ -598,6 +611,24 @@ export function AccessControlModalContent({
|
|||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={"primary"}
|
||||||
|
onClick={() => setTab("inspection")}
|
||||||
|
disabled={!canContinueToPostureChecks}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tab == "inspection" && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant={"secondary"}
|
||||||
|
onClick={() => setTab("posture_checks")}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={"primary"}
|
variant={"primary"}
|
||||||
onClick={() => setTab("general")}
|
onClick={() => setTab("general")}
|
||||||
@@ -612,7 +643,7 @@ export function AccessControlModalContent({
|
|||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
onClick={() => setTab("posture_checks")}
|
onClick={() => setTab("inspection")}
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import Badge from "@components/Badge";
|
||||||
|
import { IconCirclePlus } from "@tabler/icons-react";
|
||||||
|
import { Eye } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
|
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||||
|
import { Policy } from "@/interfaces/Policy";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
policy: Policy;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AccessControlInspectionCell({ policy }: Props) {
|
||||||
|
const { permission } = usePermissions();
|
||||||
|
const isDisabled = !permission.policies.create || !permission.policies.update;
|
||||||
|
const count = policy.inspection_policies?.length ?? 0;
|
||||||
|
|
||||||
|
return count > 0 ? (
|
||||||
|
<div className="flex">
|
||||||
|
<Badge variant="gray" useHover={true}>
|
||||||
|
<Eye size={14} className="text-netbird" />
|
||||||
|
{count} Inspection
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex">
|
||||||
|
<Badge
|
||||||
|
variant="gray"
|
||||||
|
useHover={!isDisabled}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (isDisabled) e.stopPropagation();
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
<IconCirclePlus size={14} />
|
||||||
|
Add Inspection
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import AccessControlDestinationsCell from "@/modules/access-control/table/Access
|
|||||||
import AccessControlDirectionCell from "@/modules/access-control/table/AccessControlDirectionCell";
|
import AccessControlDirectionCell from "@/modules/access-control/table/AccessControlDirectionCell";
|
||||||
import AccessControlNameCell from "@/modules/access-control/table/AccessControlNameCell";
|
import AccessControlNameCell from "@/modules/access-control/table/AccessControlNameCell";
|
||||||
import AccessControlPortsCell from "@/modules/access-control/table/AccessControlPortsCell";
|
import AccessControlPortsCell from "@/modules/access-control/table/AccessControlPortsCell";
|
||||||
|
import AccessControlInspectionCell from "@/modules/access-control/table/AccessControlInspectionCell";
|
||||||
import AccessControlPostureCheckCell from "@/modules/access-control/table/AccessControlPostureCheckCell";
|
import AccessControlPostureCheckCell from "@/modules/access-control/table/AccessControlPostureCheckCell";
|
||||||
import AccessControlProtocolCell from "@/modules/access-control/table/AccessControlProtocolCell";
|
import AccessControlProtocolCell from "@/modules/access-control/table/AccessControlProtocolCell";
|
||||||
import AccessControlSourcesCell from "@/modules/access-control/table/AccessControlSourcesCell";
|
import AccessControlSourcesCell from "@/modules/access-control/table/AccessControlSourcesCell";
|
||||||
@@ -165,6 +166,17 @@ export const AccessControlTableColumns: ColumnDef<Policy>[] = [
|
|||||||
<AccessControlPostureCheckCell policy={cell.row.original} />
|
<AccessControlPostureCheckCell policy={cell.row.original} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "inspection_policies",
|
||||||
|
accessorFn: (row) => row.inspection_policies?.length || 0,
|
||||||
|
sortingFn: "basic",
|
||||||
|
header: ({ column }) => {
|
||||||
|
return <DataTableHeader column={column}>Inspection</DataTableHeader>;
|
||||||
|
},
|
||||||
|
cell: ({ cell }) => (
|
||||||
|
<AccessControlInspectionCell policy={cell.row.original} />
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "id",
|
id: "id",
|
||||||
accessorKey: "id",
|
accessorKey: "id",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
PortRange,
|
PortRange,
|
||||||
Protocol,
|
Protocol,
|
||||||
} from "@/interfaces/Policy";
|
} from "@/interfaces/Policy";
|
||||||
|
import { InspectionPolicy } from "@/interfaces/Network";
|
||||||
import { PostureCheck } from "@/interfaces/PostureCheck";
|
import { PostureCheck } from "@/interfaces/PostureCheck";
|
||||||
import useGroupHelper from "@/modules/groups/useGroupHelper";
|
import useGroupHelper from "@/modules/groups/useGroupHelper";
|
||||||
import { usePostureCheck } from "@/modules/posture-checks/usePostureCheck";
|
import { usePostureCheck } from "@/modules/posture-checks/usePostureCheck";
|
||||||
@@ -49,6 +50,11 @@ export const useAccessControl = ({
|
|||||||
const [postureChecks, setPostureChecks] = useState<PostureCheck[]>([]);
|
const [postureChecks, setPostureChecks] = useState<PostureCheck[]>([]);
|
||||||
const postureChecksLoaded = useRef(false);
|
const postureChecksLoaded = useRef(false);
|
||||||
|
|
||||||
|
const { data: allInspectionPolicies, isLoading: isInspectionPoliciesLoading } =
|
||||||
|
useFetchApi<InspectionPolicy[]>("/inspection-policies");
|
||||||
|
const [inspectionPolicies, setInspectionPolicies] = useState<InspectionPolicy[]>([]);
|
||||||
|
const inspectionPoliciesLoaded = useRef(false);
|
||||||
|
|
||||||
const initialPostureChecks = useMemo(() => {
|
const initialPostureChecks = useMemo(() => {
|
||||||
const foundChecks =
|
const foundChecks =
|
||||||
allPostureChecks?.filter((check) => {
|
allPostureChecks?.filter((check) => {
|
||||||
@@ -82,6 +88,23 @@ export const useAccessControl = ({
|
|||||||
}
|
}
|
||||||
}, [initialPostureChecks]);
|
}, [initialPostureChecks]);
|
||||||
|
|
||||||
|
// Initialize inspection policies from existing policy
|
||||||
|
const initialInspectionPolicies = useMemo(() => {
|
||||||
|
if (!policy?.inspection_policies || !allInspectionPolicies) return [];
|
||||||
|
const ids = policy.inspection_policies.map((ip) =>
|
||||||
|
typeof ip === "string" ? ip : ip.id,
|
||||||
|
).filter(Boolean) as string[];
|
||||||
|
return allInspectionPolicies.filter((ip) => ip.id && ids.includes(ip.id));
|
||||||
|
}, [policy, allInspectionPolicies]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inspectionPoliciesLoaded.current) return;
|
||||||
|
if (initialInspectionPolicies.length > 0) {
|
||||||
|
inspectionPoliciesLoaded.current = true;
|
||||||
|
setInspectionPolicies(initialInspectionPolicies);
|
||||||
|
}
|
||||||
|
}, [initialInspectionPolicies]);
|
||||||
|
|
||||||
const { updatePolicy } = usePolicies();
|
const { updatePolicy } = usePolicies();
|
||||||
const firstRule = policy?.rules ? policy.rules[0] : undefined;
|
const firstRule = policy?.rules ? policy.rules[0] : undefined;
|
||||||
|
|
||||||
@@ -191,6 +214,7 @@ export const useAccessControl = ({
|
|||||||
description,
|
description,
|
||||||
enabled,
|
enabled,
|
||||||
source_posture_checks: postureChecks,
|
source_posture_checks: postureChecks,
|
||||||
|
inspection_policies: inspectionPolicies,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
bidirectional: direction == "bi",
|
bidirectional: direction == "bi",
|
||||||
@@ -292,6 +316,9 @@ export const useAccessControl = ({
|
|||||||
source_posture_checks: postureChecks
|
source_posture_checks: postureChecks
|
||||||
? postureChecks.map((c) => c.id)
|
? postureChecks.map((c) => c.id)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
inspection_policies: inspectionPolicies
|
||||||
|
? inspectionPolicies.map((p) => p.id)
|
||||||
|
: undefined,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
bidirectional: direction == "bi",
|
bidirectional: direction == "bi",
|
||||||
@@ -407,10 +434,13 @@ export const useAccessControl = ({
|
|||||||
setDestinationGroups,
|
setDestinationGroups,
|
||||||
postureChecks,
|
postureChecks,
|
||||||
setPostureChecks,
|
setPostureChecks,
|
||||||
|
inspectionPolicies,
|
||||||
|
setInspectionPolicies,
|
||||||
submit,
|
submit,
|
||||||
getPolicyData,
|
getPolicyData,
|
||||||
portDisabled,
|
portDisabled,
|
||||||
isPostureChecksLoading,
|
isPostureChecksLoading,
|
||||||
|
isInspectionPoliciesLoading,
|
||||||
sourceResource,
|
sourceResource,
|
||||||
setSourceResource,
|
setSourceResource,
|
||||||
destinationResource,
|
destinationResource,
|
||||||
|
|||||||
Loaded 20 of 38 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user