From 3ba7acdecfd2e70086c16fca1c52be3ee523d17d Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Mon, 23 Dec 2024 11:20:01 +0100 Subject: [PATCH] Add new networks feature (#427) --- package-lock.json | 567 ++++++++++++------ package.json | 6 +- src/app/(dashboard)/dns/settings/page.tsx | 29 +- src/app/(dashboard)/network-routes/page.tsx | 5 +- src/app/(dashboard)/network/layout.tsx | 8 + src/app/(dashboard)/network/page.tsx | 229 +++++++ src/app/(dashboard)/networks/layout.tsx | 8 + src/app/(dashboard)/networks/page.tsx | 58 ++ src/app/(dashboard)/settings/page.tsx | 7 + src/app/(dashboard)/team/user/page.tsx | 20 +- src/app/(dashboard)/team/users/page.tsx | 4 +- src/auth/OIDCError.tsx | 11 +- src/components/Button.tsx | 5 + src/components/Input.tsx | 17 +- src/components/PeerGroupSelector.tsx | 116 +++- src/components/Tabs.tsx | 7 +- src/components/VerticalTabs.tsx | 33 +- src/components/modal/ModalHeader.tsx | 15 +- src/components/table/Table.tsx | 4 +- src/components/ui/GradientFadedBackground.tsx | 2 +- src/components/ui/GroupBadge.tsx | 1 + src/components/ui/MultipleGroups.tsx | 5 +- src/components/ui/UserDropdown.tsx | 5 +- src/contexts/DialogProvider.tsx | 6 +- src/contexts/RoutesProvider.tsx | 11 +- src/interfaces/Account.ts | 1 + src/interfaces/Group.ts | 3 + src/interfaces/Network.ts | 28 + src/interfaces/Route.ts | 1 + src/layouts/Navigation.tsx | 41 +- src/layouts/PageContainer.tsx | 1 + .../access-control/AccessControlModal.tsx | 16 +- .../access-control/useAccessControl.ts | 16 +- .../access-tokens/AccessTokenActionCell.tsx | 7 +- .../access-tokens/CreateAccessTokenModal.tsx | 10 +- src/modules/account/useAccount.tsx | 2 +- src/modules/activity/ActivityDescription.tsx | 92 +++ src/modules/activity/ActivityTypeIcon.tsx | 9 + src/modules/groups/useGroupIdsToGroups.tsx | 25 + src/modules/networks/NetworkModal.tsx | 164 +++++ src/modules/networks/NetworkProvider.tsx | 340 +++++++++++ .../NetworkRangeCell.tsx} | 18 +- src/modules/networks/PolicyCell.tsx | 30 + .../misc/NetworkInformationSquare.tsx | 82 +++ .../networks/misc/NetworkNavigation.tsx | 27 + .../misc/NetworkRoutesDeprecationInfo.tsx | 23 + .../resources/NetworkResourceModal.tsx | 194 ++++++ .../networks/resources/ResourceActionCell.tsx | 39 ++ .../resources/ResourceAddressCell.tsx | 22 + .../networks/resources/ResourceGroupCell.tsx | 15 + .../networks/resources/ResourceNameCell.tsx | 32 + .../networks/resources/ResourcePolicyCell.tsx | 101 ++++ .../resources/ResourceSingleAddressInput.tsx | 65 ++ .../networks/resources/ResourceTypeCell.tsx | 22 + .../networks/resources/ResourcesSection.tsx | 68 +++ .../networks/resources/ResourcesTable.tsx | 113 ++++ .../routing-peers/NetworkRoutingPeerModal.tsx | 337 +++++++++++ .../routing-peers/NetworkRoutingPeerName.tsx | 56 ++ .../NetworkRoutingPeersSection.tsx | 70 +++ .../NetworkRoutingPeersTable.tsx | 100 +++ .../routing-peers/RoutingPeersActionCell.tsx | 40 ++ .../RoutingPeersMasqueradeCell.tsx | 48 ++ .../networks/table/NetworkActionCell.tsx | 67 +++ .../networks/table/NetworkNameCell.tsx | 25 + .../networks/table/NetworkPolicyCell.tsx | 56 ++ .../networks/table/NetworkResourceCell.tsx | 56 ++ .../networks/table/NetworkRoutingPeerCell.tsx | 108 ++++ src/modules/networks/table/NetworksTable.tsx | 194 ++++++ .../route-group/NetworkRoutesTable.tsx | 4 + src/modules/route-group/useGroupedRoutes.tsx | 2 + src/modules/routes/RouteModal.tsx | 1 - src/modules/routes/RouteTable.tsx | 4 + src/modules/routes/RouteUpdateModal.tsx | 4 + src/modules/settings/NetworkSettingsTab.tsx | 88 +++ src/modules/users/ServiceUserModal.tsx | 8 +- src/modules/users/ServiceUsersTable.tsx | 12 +- src/modules/users/UserRoleSelector.tsx | 2 + .../users/table-cells/UserActionCell.tsx | 1 + .../users/table-cells/UserGroupCell.tsx | 26 +- .../users/table-cells/UserNameCell.tsx | 5 +- .../users/table-cells/UserStatusCell.tsx | 5 +- src/utils/api.tsx | 46 +- src/utils/helpers.ts | 23 + src/utils/netbird.ts | 10 +- tailwind.config.ts | 1 + 85 files changed, 3871 insertions(+), 314 deletions(-) create mode 100644 src/app/(dashboard)/network/layout.tsx create mode 100644 src/app/(dashboard)/network/page.tsx create mode 100644 src/app/(dashboard)/networks/layout.tsx create mode 100644 src/app/(dashboard)/networks/page.tsx create mode 100644 src/interfaces/Network.ts create mode 100644 src/modules/groups/useGroupIdsToGroups.tsx create mode 100644 src/modules/networks/NetworkModal.tsx create mode 100644 src/modules/networks/NetworkProvider.tsx rename src/modules/{peer/PeerRouteNetworkCell.tsx => networks/NetworkRangeCell.tsx} (58%) create mode 100644 src/modules/networks/PolicyCell.tsx create mode 100644 src/modules/networks/misc/NetworkInformationSquare.tsx create mode 100644 src/modules/networks/misc/NetworkNavigation.tsx create mode 100644 src/modules/networks/misc/NetworkRoutesDeprecationInfo.tsx create mode 100644 src/modules/networks/resources/NetworkResourceModal.tsx create mode 100644 src/modules/networks/resources/ResourceActionCell.tsx create mode 100644 src/modules/networks/resources/ResourceAddressCell.tsx create mode 100644 src/modules/networks/resources/ResourceGroupCell.tsx create mode 100644 src/modules/networks/resources/ResourceNameCell.tsx create mode 100644 src/modules/networks/resources/ResourcePolicyCell.tsx create mode 100644 src/modules/networks/resources/ResourceSingleAddressInput.tsx create mode 100644 src/modules/networks/resources/ResourceTypeCell.tsx create mode 100644 src/modules/networks/resources/ResourcesSection.tsx create mode 100644 src/modules/networks/resources/ResourcesTable.tsx create mode 100644 src/modules/networks/routing-peers/NetworkRoutingPeerModal.tsx create mode 100644 src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx create mode 100644 src/modules/networks/routing-peers/NetworkRoutingPeersSection.tsx create mode 100644 src/modules/networks/routing-peers/NetworkRoutingPeersTable.tsx create mode 100644 src/modules/networks/routing-peers/RoutingPeersActionCell.tsx create mode 100644 src/modules/networks/routing-peers/RoutingPeersMasqueradeCell.tsx create mode 100644 src/modules/networks/table/NetworkActionCell.tsx create mode 100644 src/modules/networks/table/NetworkNameCell.tsx create mode 100644 src/modules/networks/table/NetworkPolicyCell.tsx create mode 100644 src/modules/networks/table/NetworkResourceCell.tsx create mode 100644 src/modules/networks/table/NetworkRoutingPeerCell.tsx create mode 100644 src/modules/networks/table/NetworksTable.tsx create mode 100644 src/modules/settings/NetworkSettingsTab.tsx diff --git a/package-lock.json b/package-lock.json index 5a85620..6781cce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,8 +50,8 @@ "framer-motion": "^10.16.4", "ip-cidr": "^3.1.0", "lodash": "^4.17.21", - "lucide-react": "^0.383.0", - "next": "13.5.5", + "lucide-react": "^0.460.0", + "next": "13.5.7", "next-themes": "^0.2.1", "punycode": "^2.3.1", "react": "^18", @@ -71,7 +71,7 @@ "typescript": "^5" }, "devDependencies": { - "cypress": "^13.3.3", + "cypress": "^13.13.0", "postcss": "^8", "prettier": "3.0.3", "tailwindcss": "^3" @@ -145,10 +145,11 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz", + "integrity": "sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -156,16 +157,16 @@ "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", + "form-data": "~4.0.0", + "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.10.4", + "qs": "6.13.1", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -381,9 +382,10 @@ } }, "node_modules/@next/env": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.5.tgz", - "integrity": "sha512-agvIhYWp+ilbScg81s/sLueZo8CNEYLjNOqhISxheLmD/AQI4/VxV7bV76i/KzxH4iHy/va0YS9z0AOwGnw4Fg==" + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.7.tgz", + "integrity": "sha512-uVuRqoj28Ys/AI/5gVEgRAISd0KWI0HRjOO1CTpNgmX3ZsHb5mdn14Y59yk0IxizXdo7ZjsI2S7qbWnO+GNBcA==", + "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { "version": "13.5.5", @@ -394,12 +396,13 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.5.tgz", - "integrity": "sha512-FvTdcJdTA7H1FGY8dKPPbf/O0oDC041/znHZwXA7liiGUhgw5hOQ+9z8tWvuz0M5a/SDjY/IRPBAb5FIFogYww==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.7.tgz", + "integrity": "sha512-7SxmxMex45FvKtRoP18eftrDCMyL6WQVYJSEE/s7A1AW/fCkznxjEShKet2iVVzf89gWp8HbXGaL4hCaseux6g==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -409,12 +412,13 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.5.tgz", - "integrity": "sha512-mTqNIecaojmyia7appVO2QggBe1Z2fdzxgn6jb3x9qlAk8yY2sy4MAcsj71kC9RlenCqDmr9vtC/ESFf110TPA==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.7.tgz", + "integrity": "sha512-6iENvgyIkGFLFszBL4b1VfEogKC3TDPEB6/P/lgxmgXVXIV09Q4or1MVn+U/tYyYmm7oHMZ3oxGpHAyJ80nA6g==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -424,12 +428,13 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.5.tgz", - "integrity": "sha512-U9e+kNkfvwh/T8yo+xcslvNXgyMzPPX1IbwCwnHHFmX5ckb1Uc3XZSInNjFQEQR5xhJpB5sFdal+IiBIiLYkZA==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.7.tgz", + "integrity": "sha512-P42jDX56wu9zEdVI+Xv4zyTeXB3DpqgE1Gb4bWrc0s2RIiDYr6uKBprnOs1hCGIwfVyByxyTw5Va66QCdFFNUg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -439,12 +444,13 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.5.tgz", - "integrity": "sha512-h7b58eIoNCSmKVC5fr167U0HWZ/yGLbkKD9wIller0nGdyl5zfTji0SsPKJvrG8jvKPFt2xOkVBmXlFOtuKynw==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.7.tgz", + "integrity": "sha512-A06vkj+8X+tLRzSja5REm/nqVOCzR+x5Wkw325Q/BQRyRXWGCoNbQ6A+BR5M86TodigrRfI3lUZEKZKe3QJ9Bg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -454,12 +460,13 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.5.tgz", - "integrity": "sha512-6U4y21T1J6FfcpM9uqzBJicxycpB5gJKLyQ3g6KOfBzT8H1sMwfHTRrvHKB09GIn1BCRy5YJHrA1G26DzqR46w==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.7.tgz", + "integrity": "sha512-UdHm7AlxIbdRdMsK32cH0EOX4OmzAZ4Xm+UVlS0YdvwLkI3pb7AoBEoVMG5H0Wj6Wpz6GNkrFguHTRLymTy6kw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -469,12 +476,13 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.5.tgz", - "integrity": "sha512-OuqWSAQHJQM2EsapPFTSU/FLQ0wKm7UeRNatiR/jLeCe1V02aB9xmzuWYo2Neaxxag4rss3S8fj+lvMLzwDaFA==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.7.tgz", + "integrity": "sha512-c50Y8xBKU16ZGj038H6C13iedRglxvdQHD/1BOtes56gwUrIRDX2Nkzn3mYtpz3Wzax0gfAF9C0Nqljt93IxvA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -484,12 +492,13 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.5.tgz", - "integrity": "sha512-+yLrOZIIZDY4uGn9bLOc0wTgs+M8RuOUFSUK3BhmcLav9e+tcAj0jyBHD4aXv2qWhppUeuYMsyBo1I58/eE6Dg==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.7.tgz", + "integrity": "sha512-NcUx8cmkA+JEp34WNYcKW6kW2c0JBhzJXIbw+9vKkt9m/zVJ+KfizlqmoKf04uZBtzFN6aqE2Fyv2MOd021WIA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -499,12 +508,13 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.5.tgz", - "integrity": "sha512-SyMxXyJtf9ScMH0Dh87THJMXNFvfkRAk841xyW9SeOX3KxM1buXX3hN7vof4kMGk0Yg996OGsX+7C9ueS8ugsw==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.7.tgz", + "integrity": "sha512-wXp+/3NVcuyJDED6gJiLXs5dqHaWO7moAB6aBtjlKZvsxBDxpcyjsfRbtHPeYtaT20zCkmPs69H0K25lrVZmlA==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -514,12 +524,13 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.5.tgz", - "integrity": "sha512-n5KVf2Ok0BbLwofAaHiiKf+BQCj1M8WmTujiER4/qzYAVngnsNSjqEWvJ03raeN9eURqxDO+yL5VRoDrR33H9A==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.7.tgz", + "integrity": "sha512-PLyD3Dl6jTTkLG8AoqhPGd5pXtSs8wbqIhWPQt3yEMfnYld/dGYuF2YPs3YHaVFrijCIF9pXY3+QOyvP23Zn7g==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -2496,6 +2507,7 @@ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": "~2.1.0" } @@ -2505,6 +2517,7 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } @@ -2541,7 +2554,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -2604,15 +2618,17 @@ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" }, "node_modules/axe-core": { "version": "4.7.0", @@ -2660,6 +2676,7 @@ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tweetnacl": "^0.14.3" } @@ -2801,6 +2818,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2840,7 +2886,8 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/chalk": { "version": "4.1.2", @@ -2915,9 +2962,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", "dev": true, "funding": [ { @@ -2925,6 +2972,7 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } @@ -3283,6 +3331,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3322,7 +3371,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -3364,24 +3414,25 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/cypress": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.0.tgz", - "integrity": "sha512-quIsnFmtj4dBUEJYU4OH0H12bABJpSujvWexC24Ju1gTlKMJbeT6tTO0vh7WNfiBPPjoIXLN+OUqVtiKFs6SGw==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", + "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { - "@cypress/request": "^3.0.0", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", - "buffer": "^5.6.0", + "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -3396,7 +3447,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.0", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -3410,7 +3460,8 @@ "request-progress": "^3.0.0", "semver": "^7.5.3", "supports-color": "^8.1.1", - "tmp": "~0.2.1", + "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -3421,15 +3472,6 @@ "node": "^16.0.0 || ^18.0.0 || >=20.0.0" } }, - "node_modules/cypress/node_modules/@types/node": { - "version": "18.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.12.tgz", - "integrity": "sha512-G7slVfkwOm7g8VqcEF1/5SXiMjP3Tbt+pXDU3r/qhlM2KkGm786DUD4xyMA2QzEElFrv/KZV9gjygv4LnkpbMQ==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -3440,6 +3482,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" }, @@ -3527,6 +3570,7 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -3576,6 +3620,20 @@ "node": ">=6.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/easy-bem": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/easy-bem/-/easy-bem-1.1.1.tgz", @@ -3586,6 +3644,7 @@ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, + "license": "MIT", "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -3595,7 +3654,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.4.592", @@ -3693,6 +3753,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", @@ -3714,6 +3792,18 @@ "safe-array-concat": "^1.0.1" } }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", @@ -4220,7 +4310,8 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/extract-zip": { "version": "2.0.1", @@ -4249,7 +4340,8 @@ "dev": true, "engines": [ "node >=0.6.0" - ] + ], + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -4427,22 +4519,24 @@ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, "node_modules/fraction.js": { @@ -4547,14 +4641,24 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4623,6 +4727,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" } @@ -4733,11 +4838,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4792,9 +4898,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4817,9 +4924,10 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -4828,14 +4936,15 @@ } }, "node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", - "sshpk": "^1.14.1" + "sshpk": "^1.18.0" }, "engines": { "node": ">=0.10" @@ -5058,18 +5167,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -5305,7 +5402,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -5364,7 +5462,8 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/iterator.prototype": { "version": "1.1.2", @@ -5416,7 +5515,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -5432,7 +5532,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/json5": { "version": "1.0.2", @@ -5465,6 +5566,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -5689,11 +5791,12 @@ } }, "node_modules/lucide-react": { - "version": "0.383.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.383.0.tgz", - "integrity": "sha512-13xlG0CQCJtzjSQYwwJ3WRqMHtRj3EXmLlorrARt7y+IHnxUCp3XyFNL1DfaGySWxHObDvnu1u1dV+0VMKHUSg==", + "version": "0.460.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz", + "integrity": "sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==", + "license": "ISC", "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, "node_modules/matchmediaquery": { @@ -5704,6 +5807,15 @@ "css-mediaquery": "^0.1.2" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5735,6 +5847,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5744,6 +5857,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -5825,11 +5939,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/next": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/next/-/next-13.5.5.tgz", - "integrity": "sha512-LddFJjpfrtrMMw8Q9VLhIURuSidiCNcMQjRqcPtrKd+Fx07MsG7hYndJb/f2d3I+mTbTotsTJfCnn0eZ/YPk8w==", + "version": "13.5.7", + "resolved": "https://registry.npmjs.org/next/-/next-13.5.7.tgz", + "integrity": "sha512-W7KIRTE+hPcgGdq89P3mQLDX3m7pJ6nxSyC+YxYaUExE+cS4UledB+Ntk98tKoyhsv6fjb2TRAnD7VDvoqmeFg==", + "license": "MIT", "dependencies": { - "@next/env": "13.5.5", + "@next/env": "13.5.7", "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -5844,15 +5959,15 @@ "node": ">=16.14.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.5.5", - "@next/swc-darwin-x64": "13.5.5", - "@next/swc-linux-arm64-gnu": "13.5.5", - "@next/swc-linux-arm64-musl": "13.5.5", - "@next/swc-linux-x64-gnu": "13.5.5", - "@next/swc-linux-x64-musl": "13.5.5", - "@next/swc-win32-arm64-msvc": "13.5.5", - "@next/swc-win32-ia32-msvc": "13.5.5", - "@next/swc-win32-x64-msvc": "13.5.5" + "@next/swc-darwin-arm64": "13.5.7", + "@next/swc-darwin-x64": "13.5.7", + "@next/swc-linux-arm64-gnu": "13.5.7", + "@next/swc-linux-arm64-musl": "13.5.7", + "@next/swc-linux-x64-gnu": "13.5.7", + "@next/swc-linux-x64-musl": "13.5.7", + "@next/swc-win32-arm64-msvc": "13.5.7", + "@next/swc-win32-ia32-msvc": "13.5.7", + "@next/swc-win32-x64-msvc": "13.5.7" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -5929,9 +6044,13 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6175,7 +6294,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.0.0", @@ -6407,12 +6527,6 @@ "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", "dev": true }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -6432,12 +6546,13 @@ } }, "node_modules/qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -6446,12 +6561,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6763,12 +6872,6 @@ "throttleit": "^1.0.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -6909,7 +7012,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-regex-test": { "version": "1.0.0", @@ -6928,7 +7032,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/scheduler": { "version": "0.23.0", @@ -7004,13 +7109,72 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7062,6 +7226,7 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, + "license": "MIT", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -7086,7 +7251,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/streamsearch": { "version": "1.1.0", @@ -7429,16 +7595,34 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "node_modules/tldts": { + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.69.tgz", + "integrity": "sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==", "dev": true, + "license": "MIT", "dependencies": { - "rimraf": "^3.0.0" + "tldts-core": "^6.1.69" }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.69.tgz", + "integrity": "sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/to-regex-range": { @@ -7453,27 +7637,26 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" + "node": ">=16" } }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, - "engines": { - "node": ">= 4.0.0" + "license": "MIT", + "bin": { + "tree-kill": "cli.js" } }, "node_modules/ts-api-utils": { @@ -7513,6 +7696,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -7524,7 +7708,8 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true + "dev": true, + "license": "Unlicense" }, "node_modules/type-check": { "version": "0.4.0", @@ -7695,16 +7880,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/use-callback-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", @@ -7764,6 +7939,7 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -7776,6 +7952,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", diff --git a/package.json b/package.json index 70e1d3a..5dc826a 100644 --- a/package.json +++ b/package.json @@ -55,8 +55,8 @@ "framer-motion": "^10.16.4", "ip-cidr": "^3.1.0", "lodash": "^4.17.21", - "lucide-react": "^0.383.0", - "next": "13.5.5", + "lucide-react": "^0.460.0", + "next": "13.5.7", "next-themes": "^0.2.1", "punycode": "^2.3.1", "react": "^18", @@ -76,7 +76,7 @@ "typescript": "^5" }, "devDependencies": { - "cypress": "^13.3.3", + "cypress": "^13.13.0", "postcss": "^8", "prettier": "3.0.3", "tailwindcss": "^3" diff --git a/src/app/(dashboard)/dns/settings/page.tsx b/src/app/(dashboard)/dns/settings/page.tsx index ffbcb36..866f06d 100644 --- a/src/app/(dashboard)/dns/settings/page.tsx +++ b/src/app/(dashboard)/dns/settings/page.tsx @@ -14,17 +14,24 @@ import { IconSettings2 } from "@tabler/icons-react"; import useFetchApi, { useApiCall } from "@utils/api"; import { ExternalLinkIcon } from "lucide-react"; import React from "react"; +import Skeleton from "react-loading-skeleton"; import { useSWRConfig } from "swr"; import DNSIcon from "@/assets/icons/DNSIcon"; import { useHasChanges } from "@/hooks/useHasChanges"; +import { Group } from "@/interfaces/Group"; import { NameserverSettings } from "@/interfaces/NameserverSettings"; import PageContainer from "@/layouts/PageContainer"; import useGroupHelper from "@/modules/groups/useGroupHelper"; +import { useGroupIdsToGroups } from "@/modules/groups/useGroupIdsToGroups"; export default function NameServerSettings() { const { data: settings, isLoading } = useFetchApi("/dns/settings"); + const initialDNSGroups = useGroupIdsToGroups( + settings?.disabled_management_groups, + ); + return (
@@ -55,10 +62,16 @@ export default function NameServerSettings() { in our documentation. - {!isLoading && ( - + {!isLoading && initialDNSGroups !== undefined ? ( + + ) : ( +
+ +
)}
@@ -67,16 +80,16 @@ export default function NameServerSettings() { } const SettingDisabledManagementGroups = ({ - initial, + initialGroups, }: { - initial: string[] | undefined; + initialGroups: Group[]; }) => { const settingRequest = useApiCall("/dns/settings"); const { mutate } = useSWRConfig(); const [selectedGroups, setSelectedGroups, { save: saveGroups }] = useGroupHelper({ - initial: initial || [], + initial: initialGroups, }); const { hasChanges, updateRef: updateChangesRef } = useHasChanges([ @@ -108,6 +121,7 @@ const SettingDisabledManagementGroups = ({ Peers in these groups will require manual domain name resolution @@ -122,6 +136,7 @@ const SettingDisabledManagementGroups = ({ size={"sm"} onClick={saveSettings} disabled={!hasChanges} + data-cy={"save-changes"} > Save Changes diff --git a/src/app/(dashboard)/network-routes/page.tsx b/src/app/(dashboard)/network-routes/page.tsx index e5b0098..c4646cc 100644 --- a/src/app/(dashboard)/network-routes/page.tsx +++ b/src/app/(dashboard)/network-routes/page.tsx @@ -14,6 +14,7 @@ import PeersProvider from "@/contexts/PeersProvider"; import RoutesProvider from "@/contexts/RoutesProvider"; import { Route } from "@/interfaces/Route"; import PageContainer from "@/layouts/PageContainer"; +import { NetworkRoutesDeprecationInfo } from "@/modules/networks/misc/NetworkRoutesDeprecationInfo"; import useGroupedRoutes from "@/modules/route-group/useGroupedRoutes"; const NetworkRoutesTable = lazy( @@ -39,7 +40,9 @@ export default function NetworkRoutes() { icon={} /> -

Network Routes

+

+ Network Routes +

Network routes allow you to access other networks like LANs and VPCs without installing NetBird on every resource. diff --git a/src/app/(dashboard)/network/layout.tsx b/src/app/(dashboard)/network/layout.tsx new file mode 100644 index 0000000..7896c60 --- /dev/null +++ b/src/app/(dashboard)/network/layout.tsx @@ -0,0 +1,8 @@ +import { globalMetaTitle } from "@utils/meta"; +import type { Metadata } from "next"; +import BlankLayout from "@/layouts/BlankLayout"; + +export const metadata: Metadata = { + title: `Network - Networks - ${globalMetaTitle}`, +}; +export default BlankLayout; diff --git a/src/app/(dashboard)/network/page.tsx b/src/app/(dashboard)/network/page.tsx new file mode 100644 index 0000000..b86033a --- /dev/null +++ b/src/app/(dashboard)/network/page.tsx @@ -0,0 +1,229 @@ +"use client"; + +import Breadcrumbs from "@components/Breadcrumbs"; +import Card from "@components/Card"; +import FullTooltip from "@components/FullTooltip"; +import InlineLink from "@components/InlineLink"; +import Separator from "@components/Separator"; +import FullScreenLoading from "@components/ui/FullScreenLoading"; +import useRedirect from "@hooks/useRedirect"; +import useFetchApi from "@utils/api"; +import { cn } from "@utils/helpers"; +import { + ArrowUpRightIcon, + HelpCircle, + PencilLineIcon, + ServerIcon, + ShieldCheckIcon, + ShieldXIcon, +} from "lucide-react"; +import { useSearchParams } from "next/navigation"; +import React, { useMemo, useState } from "react"; +import { useSWRConfig } from "swr"; +import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon"; +import { useLoggedInUser } from "@/contexts/UsersProvider"; +import { Network } from "@/interfaces/Network"; +import PageContainer from "@/layouts/PageContainer"; +import { NetworkInformationSquare } from "@/modules/networks/misc/NetworkInformationSquare"; +import NetworkModal from "@/modules/networks/NetworkModal"; +import { NetworkProvider } from "@/modules/networks/NetworkProvider"; +import { ResourcesSection } from "@/modules/networks/resources/ResourcesSection"; +import { NetworkRoutingPeersSection } from "@/modules/networks/routing-peers/NetworkRoutingPeersSection"; + +export default function NetworkDetailPage() { + const queryParameter = useSearchParams(); + const networkId = queryParameter.get("id"); + const { data: network, isLoading } = useFetchApi( + `/networks/${networkId}`, + true, + ); + + useRedirect("/networks", false, !networkId); + + return network && !isLoading ? ( + + ) : ( + + ); +} + +function NetworkOverview({ network }: Readonly<{ network: Network }>) { + const { isUser } = useLoggedInUser(); + const [networkModal, setNetworkModal] = useState(false); + const { mutate } = useSWRConfig(); + + const isActive = !!( + network?.routing_peers_count && network.routing_peers_count > 0 + ); + + return ( + + +
+ + } + /> + + + +
+
+ + + { + mutate(`/networks/${network.id}`); + }} + network={network} + /> +
+
+ +
+ +
+
+ + + +
+ + + + + ); +} + +function NetworkInformationCard({ network }: Readonly<{ network: Network }>) { + const isHighlyAvailable = !!( + network?.routing_peers_count && network?.routing_peers_count >= 2 + ); + + const disabledText = useMemo( + () => ( + <> + High availability is currently{" "} + inactive for this + network. + + ), + [], + ); + + const enabledText = useMemo( + () => ( + <> + High availability is{" "} + active for this + network. + + ), + [], + ); + + const policyCount = network.policies?.length ?? 0; + + return ( + + + + + High Availability + + } + value={ + + {isHighlyAvailable ? enabledText : disabledText} + {isHighlyAvailable ? ( +
+ You can add more routing peers to increase the + availability of this network. +
+ ) : ( +
+ Go ahead and add more routing peers or groups with routing + peers to enable high availability for this network. +
+ )} +
+ } + > +
+ + {isHighlyAvailable ? "Active" : "Inactive"} + +
+ + } + /> + 0 ? ( + <> + + {policyCount}{" "} + {policyCount === 1 ? "Active Policy" : "Active Policies"} + + ) : ( + <> + + No Active Policies + + ) + } + value={ + policyCount > 0 ? ( + + Go to Policies + + + ) : null + } + /> + + + ); +} diff --git a/src/app/(dashboard)/networks/layout.tsx b/src/app/(dashboard)/networks/layout.tsx new file mode 100644 index 0000000..9e71f33 --- /dev/null +++ b/src/app/(dashboard)/networks/layout.tsx @@ -0,0 +1,8 @@ +import { globalMetaTitle } from "@utils/meta"; +import type { Metadata } from "next"; +import BlankLayout from "@/layouts/BlankLayout"; + +export const metadata: Metadata = { + title: `Networks - ${globalMetaTitle}`, +}; +export default BlankLayout; diff --git a/src/app/(dashboard)/networks/page.tsx b/src/app/(dashboard)/networks/page.tsx new file mode 100644 index 0000000..e6ea6b4 --- /dev/null +++ b/src/app/(dashboard)/networks/page.tsx @@ -0,0 +1,58 @@ +"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 { usePortalElement } from "@hooks/usePortalElement"; +import useFetchApi from "@utils/api"; +import { ExternalLinkIcon } from "lucide-react"; +import React, { Suspense } from "react"; +import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon"; +import { Network } from "@/interfaces/Network"; +import PageContainer from "@/layouts/PageContainer"; +import NetworksTable from "@/modules/networks/table/NetworksTable"; + +export default function Networks() { + const { data: networks, isLoading } = useFetchApi("/networks"); + const { ref: headingRef, portalTarget } = + usePortalElement(); + + return ( + +
+ + } + /> + +

Networks

+ + Networks allow you to access other resources like LANs and VPCs + without installing NetBird on every device. + + + Learn more about + + Networks + + + in our documentation. + +
+ + + }> + + + +
+ ); +} diff --git a/src/app/(dashboard)/settings/page.tsx b/src/app/(dashboard)/settings/page.tsx index fd5f427..9f878d2 100644 --- a/src/app/(dashboard)/settings/page.tsx +++ b/src/app/(dashboard)/settings/page.tsx @@ -6,6 +6,7 @@ import { AlertOctagonIcon, FolderGit2Icon, LockIcon, + NetworkIcon, ShieldIcon, } from "lucide-react"; import { useSearchParams } from "next/navigation"; @@ -16,6 +17,7 @@ import { useAccount } from "@/modules/account/useAccount"; import AuthenticationTab from "@/modules/settings/AuthenticationTab"; import DangerZoneTab from "@/modules/settings/DangerZoneTab"; import GroupsTab from "@/modules/settings/GroupsTab"; +import NetworkSettingsTab from "@/modules/settings/NetworkSettingsTab"; import PermissionsTab from "@/modules/settings/PermissionsTab"; export default function NetBirdSettings() { @@ -47,6 +49,10 @@ export default function NetBirdSettings() { Permissions + + + Networks + Danger zone @@ -57,6 +63,7 @@ export default function NetBirdSettings() { {account && } {account && } {account && } + {account && } {account && } diff --git a/src/app/(dashboard)/team/user/page.tsx b/src/app/(dashboard)/team/user/page.tsx index d42d823..4fd68a7 100644 --- a/src/app/(dashboard)/team/user/page.tsx +++ b/src/app/(dashboard)/team/user/page.tsx @@ -22,11 +22,13 @@ import { useSWRConfig } from "swr"; import TeamIcon from "@/assets/icons/TeamIcon"; import { useLoggedInUser } from "@/contexts/UsersProvider"; import { useHasChanges } from "@/hooks/useHasChanges"; +import { Group } from "@/interfaces/Group"; import { Role, User } from "@/interfaces/User"; import PageContainer from "@/layouts/PageContainer"; import AccessTokensTable from "@/modules/access-tokens/AccessTokensTable"; import CreateAccessTokenModal from "@/modules/access-tokens/CreateAccessTokenModal"; import useGroupHelper from "@/modules/groups/useGroupHelper"; +import { useGroupIdsToGroups } from "@/modules/groups/useGroupIdsToGroups"; import UserBlockCell from "@/modules/users/table-cells/UserBlockCell"; import UserStatusCell from "@/modules/users/table-cells/UserStatusCell"; import { UserRoleSelector } from "@/modules/users/UserRoleSelector"; @@ -45,8 +47,10 @@ export default function UserPage() { useRedirect("/team/users", false, !userId); - return !isLoading && user ? ( - + const userGroups = useGroupIdsToGroups(user?.auto_groups); + + return !isLoading && user && userGroups !== undefined ? ( + ) : ( ); @@ -54,16 +58,16 @@ export default function UserPage() { type Props = { user: User; + initialGroups: Group[]; }; -function UserOverview({ user }: Props) { +function UserOverview({ user, initialGroups }: Readonly) { const router = useRouter(); const userRequest = useApiCall("/users"); const { mutate } = useSWRConfig(); const { loggedInUser, isOwnerOrAdmin, isUser } = useLoggedInUser(); const isLoggedInUser = loggedInUser ? loggedInUser?.id === user.id : false; - const initialGroups = user.auto_groups; const [selectedGroups, setSelectedGroups, { save: saveGroups }] = useGroupHelper({ initial: initialGroups, @@ -180,6 +184,7 @@ function UserOverview({ user }: Props) { className={"w-full"} disabled={!hasChanges} onClick={save} + data-cy={"save-changes"} > Save Changes @@ -201,6 +206,7 @@ function UserOverview({ user }: Props) { onChange={setSelectedGroups} values={selectedGroups} hideAllGroup={true} + dataCy={"user-group-selector"} /> )} @@ -244,7 +250,10 @@ function UserOverview({ user }: Props) {
- @@ -293,6 +302,7 @@ function UserInformationCard({ user }: { user: User }) { )} diff --git a/src/app/(dashboard)/team/users/page.tsx b/src/app/(dashboard)/team/users/page.tsx index 9744224..c0bdeb6 100644 --- a/src/app/(dashboard)/team/users/page.tsx +++ b/src/app/(dashboard)/team/users/page.tsx @@ -10,12 +10,14 @@ import useFetchApi from "@utils/api"; import { ExternalLinkIcon, User2 } from "lucide-react"; import React, { lazy, Suspense } from "react"; import TeamIcon from "@/assets/icons/TeamIcon"; +import { useGroups } from "@/contexts/GroupsProvider"; import { User } from "@/interfaces/User"; import PageContainer from "@/layouts/PageContainer"; const UsersTable = lazy(() => import("@/modules/users/UsersTable")); export default function TeamUsers() { + const { isLoading: isGroupsLoading } = useGroups(); const { data: users, isLoading } = useFetchApi( "/users?service_user=false", ); @@ -60,7 +62,7 @@ export default function TeamUsers() { }> diff --git a/src/auth/OIDCError.tsx b/src/auth/OIDCError.tsx index c857b50..aa04d88 100644 --- a/src/auth/OIDCError.tsx +++ b/src/auth/OIDCError.tsx @@ -15,7 +15,9 @@ export const OIDCError = () => { const params = useSearchParams(); const errorParam = params.get("error"); const accessDenied = errorParam === "access_denied"; + const invalidRequest = errorParam === "invalid_request"; const [title, setTitle] = useState(params.get("error_description")); + const errorDescription = params.get("error_description"); const { logout, login } = useOidc(); useEffect(() => { @@ -72,9 +74,14 @@ export const OIDCError = () => { ) : ( <> - + There was an error logging you in.
- Error: {oidcUserLoadingState} + Error:{" "} + + {invalidRequest && errorDescription + ? errorDescription + : oidcUserLoadingState} +
-
+
{searchedGroupNotFound && ( )} + +
); } + +const ResourcesCounter = ({ group }: { group: Group }) => { + return group?.resources_count && group.resources_count > 0 ? ( +
+ + {group.resources_count} Resource(s) +
+ ) : null; +}; + +const resourcesSearchPredicate = (item: NetworkResource, query: string) => { + const lowerCaseQuery = query.toLowerCase(); + if (item.name.toLowerCase().includes(lowerCaseQuery)) return true; + return item.address.toLowerCase().includes(lowerCaseQuery); +}; + +const ResourcesList = ({ search }: { search: string }) => { + const { data: resources, isLoading } = useFetchApi( + "/networks/resources", + ); + + const [filteredItems, _, setSearch] = useSearch( + resources || [], + resourcesSearchPredicate, + { filter: true, debounce: 150 }, + ); + + useEffect(() => { + setSearch(search); + }, [search, setSearch]); + + return isLoading ? ( + <>Loading... + ) : ( + filteredItems.length > 0 && ( + null} + renderItem={(res) => { + const isSelected = false; + + return ( + +
+ { + e.preventDefault(); + }} + > + {res.type === "host" && ( + + )} + {res.type === "domain" && ( + + )} + {res.type === "subnet" && ( + + )} + + + +
+ +
+
+ +
+
+
+ ); + }} + /> + ) + ); +}; diff --git a/src/components/Tabs.tsx b/src/components/Tabs.tsx index 04b3b5b..9f2c712 100644 --- a/src/components/Tabs.tsx +++ b/src/components/Tabs.tsx @@ -39,7 +39,7 @@ const Tabs = React.forwardRef< Tabs.displayName = TabsPrimitive.Root.displayName; type TabListProps = { - justify?: "start" | "end" | "center"; + justify?: "start" | "end" | "center" | "between"; }; const TabsList = React.forwardRef< @@ -54,6 +54,7 @@ const TabsList = React.forwardRef< justify == "center" && "justify-center justify-items-end", justify == "start" && "justify-start", justify == "end" && "justify-end", + justify == "between" && "justify-between", )} {...props} > @@ -63,7 +64,9 @@ const TabsList = React.forwardRef< } /> -
{props.children}
+
+ {props.children} +
diff --git a/src/components/VerticalTabs.tsx b/src/components/VerticalTabs.tsx index 56897ea..d894662 100644 --- a/src/components/VerticalTabs.tsx +++ b/src/components/VerticalTabs.tsx @@ -11,17 +11,36 @@ type Props = { onChange: (value: string) => void; children: React.ReactNode; }; + +const TabSwitchContext = React.createContext<{ + switchTab: (value: string) => void; +}>({ + switchTab: () => {}, +}); + +export const useTabSwitchContext = () => { + return React.useContext(TabSwitchContext); +}; + function VerticalTabs({ value, onChange, children }: Props) { return ( - onChange(value)} + { + onChange(value); + }, + }} > - {children} - + onChange(value)} + > + {children} + + ); } diff --git a/src/components/modal/ModalHeader.tsx b/src/components/modal/ModalHeader.tsx index 0da6a26..4c9b716 100644 --- a/src/components/modal/ModalHeader.tsx +++ b/src/components/modal/ModalHeader.tsx @@ -11,6 +11,7 @@ interface Props extends IconVariant { margin?: string; truncate?: boolean; children?: React.ReactNode; + center?: boolean; } export default function ModalHeader({ icon, @@ -21,13 +22,21 @@ export default function ModalHeader({ margin = "mt-0", truncate = false, children, + center, }: Props) { return (
-
+
{icon && } -
-

{title}

+
+

+ {title} +

{children ? ( <>{children} ) : ( diff --git a/src/components/table/Table.tsx b/src/components/table/Table.tsx index 0cbf5be..fc11992 100644 --- a/src/components/table/Table.tsx +++ b/src/components/table/Table.tsx @@ -101,11 +101,11 @@ const TableRow = React.forwardRef< { return (
{ diff --git a/src/components/ui/MultipleGroups.tsx b/src/components/ui/MultipleGroups.tsx index d269590..8b34787 100644 --- a/src/components/ui/MultipleGroups.tsx +++ b/src/components/ui/MultipleGroups.tsx @@ -34,7 +34,10 @@ export default function MultipleGroups({ -
+
{firstGroup && } {otherGroups && otherGroups.length > 0 && ( logout(), []); - - const [dropdownOpen, setDropdownOpen] = useState(false); + useHotkeys("shift+mod+l", () => logoutSession(), []); const { permission } = useLoggedInUser(); + const [dropdownOpen, setDropdownOpen] = useState(false); return ( , danger: , + center: "", }; return ( @@ -61,8 +62,9 @@ export default function DialogProvider({ children }: Props) { onOpenChange={(open) => fn.current && fn.current(open)} > {dialogOptions && ( - + , onSuccess?: (route: Route) => void, message?: string, + options?: { remove_access_control_groups?: boolean }, ) => void; }, ); @@ -33,6 +34,7 @@ export default function RoutesProvider({ children }: Readonly) { toUpdate: Partial, onSuccess?: (route: Route) => void, message?: string, + options?: { remove_access_control_groups?: boolean }, ) => { const hasDomains = route.domains ? route.domains.length > 0 : false; @@ -54,10 +56,11 @@ export default function RoutesProvider({ children }: Readonly) { metric: toUpdate.metric ?? route.metric ?? 9999, masquerade: toUpdate.masquerade ?? route.masquerade ?? true, groups: toUpdate.groups ?? route.groups ?? [], - access_control_groups: - toUpdate.access_control_groups ?? - route.access_control_groups ?? - undefined, + access_control_groups: options?.remove_access_control_groups + ? undefined + : toUpdate.access_control_groups ?? + route.access_control_groups ?? + undefined, }, `/${route.id}`, ) diff --git a/src/interfaces/Account.ts b/src/interfaces/Account.ts index c951505..73f1545 100644 --- a/src/interfaces/Account.ts +++ b/src/interfaces/Account.ts @@ -11,5 +11,6 @@ export interface Account { jwt_groups_claim_name: string; jwt_allow_groups: string[]; regular_users_view_blocked: boolean; + routing_peer_dns_resolution_enabled: boolean; }; } diff --git a/src/interfaces/Group.ts b/src/interfaces/Group.ts index 301c80c..8b800fc 100644 --- a/src/interfaces/Group.ts +++ b/src/interfaces/Group.ts @@ -3,6 +3,9 @@ export interface Group { name: string; peers?: GroupPeer[] | string[]; peers_count?: number; + resources?: string[]; + resources_count?: number; + // Frontend only keepClientState?: boolean; } diff --git a/src/interfaces/Network.ts b/src/interfaces/Network.ts new file mode 100644 index 0000000..1e9f37a --- /dev/null +++ b/src/interfaces/Network.ts @@ -0,0 +1,28 @@ +import { Group } from "@/interfaces/Group"; + +export interface Network { + id: string; + name: string; + description?: string; + resources?: string[]; + policies?: string[]; + routers?: string[]; + routing_peers_count?: number; +} + +export interface NetworkRouter { + id: string; + peer?: string; + peer_groups?: string[]; + metric: number; + masquerade: boolean; +} + +export interface NetworkResource { + id: string; + name: string; + description?: string; + address: string; + groups?: string[] | Group[]; + type?: "domain" | "host" | "subnet"; +} diff --git a/src/interfaces/Route.ts b/src/interfaces/Route.ts index 46527ec..1e13c0c 100644 --- a/src/interfaces/Route.ts +++ b/src/interfaces/Route.ts @@ -35,4 +35,5 @@ export interface GroupedRoute { description?: string; description_search?: string; domain_search?: string; + routes_search?: string; } diff --git a/src/layouts/Navigation.tsx b/src/layouts/Navigation.tsx index 2514b64..1b3771c 100644 --- a/src/layouts/Navigation.tsx +++ b/src/layouts/Navigation.tsx @@ -8,7 +8,6 @@ import AccessControlIcon from "@/assets/icons/AccessControlIcon"; import ActivityIcon from "@/assets/icons/ActivityIcon"; import DNSIcon from "@/assets/icons/DNSIcon"; import DocsIcon from "@/assets/icons/DocsIcon"; -import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon"; import PeerIcon from "@/assets/icons/PeerIcon"; import SettingsIcon from "@/assets/icons/SettingsIcon"; import SetupKeysIcon from "@/assets/icons/SetupKeysIcon"; @@ -17,6 +16,7 @@ import SidebarItem from "@/components/SidebarItem"; import { useAnnouncement } from "@/contexts/AnnouncementProvider"; import { useLoggedInUser } from "@/contexts/UsersProvider"; import { headerHeight } from "@/layouts/Header"; +import { NetworkNavigation } from "@/modules/networks/misc/NetworkNavigation"; const customTheme: CustomFlowbiteTheme["sidebar"] = { root: { @@ -34,6 +34,7 @@ export default function Navigation({ hideOnMobile = false, }: Props) { const { isUser } = useLoggedInUser(); + const { isOwnerOrAdmin } = useLoggedInUser(); const { bannerHeight } = useAnnouncement(); return ( @@ -104,11 +105,8 @@ export default function Navigation({ /> - } - label="Network Routes" - href={"/network-routes"} - /> + + } label="DNS" @@ -141,33 +139,24 @@ export default function Navigation({ /> )} - - {isUser && ( - } - href={"https://docs.netbird.io/"} - target={"_blank"} - label="Documentation" - /> - )} - {!isUser && ( - + + + {isOwnerOrAdmin && ( } label="Settings" href={"/settings"} exactPathMatch={true} /> - - } - href={"https://docs.netbird.io/"} - target={"_blank"} - label="Documentation" - /> - - )} + )} + } + href={"https://docs.netbird.io/"} + target={"_blank"} + label="Documentation" + /> +
diff --git a/src/layouts/PageContainer.tsx b/src/layouts/PageContainer.tsx index 5a45dd7..e033e50 100644 --- a/src/layouts/PageContainer.tsx +++ b/src/layouts/PageContainer.tsx @@ -11,6 +11,7 @@ export default function PageContainer({ children, className }: Props) { className={cn( className, "relative flex-auto overflow-auto bg-nb-gray z-1", + "focus:outline-none", )} > {children} diff --git a/src/modules/access-control/AccessControlModal.tsx b/src/modules/access-control/AccessControlModal.tsx index 3ec3711..e8816cb 100644 --- a/src/modules/access-control/AccessControlModal.tsx +++ b/src/modules/access-control/AccessControlModal.tsx @@ -41,6 +41,7 @@ import { } from "lucide-react"; import React, { useMemo, useState } from "react"; import AccessControlIcon from "@/assets/icons/AccessControlIcon"; +import { Group } from "@/interfaces/Group"; import { Policy, Protocol } from "@/interfaces/Policy"; import { PostureCheck } from "@/interfaces/PostureCheck"; import { useAccessControl } from "@/modules/access-control/useAccessControl"; @@ -105,6 +106,9 @@ export function AccessControlUpdateModal({ type ModalProps = { onSuccess?: (p: Policy) => void; policy?: Policy; + initialDestinationGroups?: Group[] | string[]; + initialName?: string; + initialDescription?: string; cell?: string; postureCheckTemplates?: PostureCheck[]; useSave?: boolean; @@ -118,6 +122,9 @@ export function AccessControlModalContent({ postureCheckTemplates, useSave = true, allowEditPeers = false, + initialDestinationGroups, + initialName, + initialDescription, }: Readonly) { const { portAndDirectionDisabled, @@ -142,7 +149,14 @@ export function AccessControlModalContent({ submit, isPostureChecksLoading, getPolicyData, - } = useAccessControl({ policy, postureCheckTemplates, onSuccess }); + } = useAccessControl({ + policy, + postureCheckTemplates, + onSuccess, + initialDestinationGroups, + initialName, + initialDescription, + }); const [tab, setTab] = useState(() => { if (!cell) return "policy"; diff --git a/src/modules/access-control/useAccessControl.ts b/src/modules/access-control/useAccessControl.ts index 7df411e..0dd641c 100644 --- a/src/modules/access-control/useAccessControl.ts +++ b/src/modules/access-control/useAccessControl.ts @@ -15,6 +15,9 @@ type Props = { policy?: Policy; postureCheckTemplates?: PostureCheck[]; onSuccess?: (policy: Policy) => void; + initialDestinationGroups?: Group[] | string[]; + initialName?: string; + initialDescription?: string; }; // TODO add reducer @@ -22,6 +25,9 @@ type Props = { export const useAccessControl = ({ policy, postureCheckTemplates, + initialDestinationGroups, + initialName, + initialDescription, onSuccess, }: Props = {}) => { const { data: allPostureChecks, isLoading: isPostureChecksLoading } = @@ -85,8 +91,10 @@ export const useAccessControl = ({ if (firstRule && firstRule?.bidirectional == false) return "in"; return "bi"; }); - const [name, setName] = useState(policy?.name || ""); - const [description, setDescription] = useState(policy?.description || ""); + const [name, setName] = useState(policy?.name || initialName || ""); + const [description, setDescription] = useState( + policy?.description || initialDescription || "", + ); const { mutate } = useSWRConfig(); const policyRequest = useApiCall("/policies"); @@ -104,7 +112,9 @@ export const useAccessControl = ({ setDestinationGroups, { getGroupsToUpdate: getDestinationGroupsToUpdate }, ] = useGroupHelper({ - initial: firstRule ? (firstRule.destinations as Group[]) : [], + initial: firstRule + ? (firstRule.destinations as Group[]) + : initialDestinationGroups ?? [], }); const { updateOrCreateAndNotify: checkToCreate } = usePostureCheck({}); diff --git a/src/modules/access-tokens/AccessTokenActionCell.tsx b/src/modules/access-tokens/AccessTokenActionCell.tsx index 8944a7e..fc63a10 100644 --- a/src/modules/access-tokens/AccessTokenActionCell.tsx +++ b/src/modules/access-tokens/AccessTokenActionCell.tsx @@ -46,7 +46,12 @@ export default function AccessTokenActionCell({ access_token }: Props) { return (
- diff --git a/src/modules/access-tokens/CreateAccessTokenModal.tsx b/src/modules/access-tokens/CreateAccessTokenModal.tsx index 4e75ff0..876e0a5 100644 --- a/src/modules/access-tokens/CreateAccessTokenModal.tsx +++ b/src/modules/access-tokens/CreateAccessTokenModal.tsx @@ -98,6 +98,7 @@ export default function CreateAccessTokenModal({ children, user }: Props) { variant={"secondary"} className={"w-full"} tabIndex={-1} + data-cy={"access-token-copy-close"} > Close @@ -170,6 +171,7 @@ export function AccessTokenModalContent({ onSuccess, user }: ModalProps) { Set an easily identifiable name for your token setName(e.target.value)} @@ -184,6 +186,7 @@ export function AccessTokenModalContent({ onSuccess, user }: ModalProps) { Cancel - diff --git a/src/modules/account/useAccount.tsx b/src/modules/account/useAccount.tsx index 880d8b0..cc3f89f 100644 --- a/src/modules/account/useAccount.tsx +++ b/src/modules/account/useAccount.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { Account } from "@/interfaces/Account"; export const useAccount = () => { - const { data: accounts } = useFetchApi("/accounts"); + const { data: accounts } = useFetchApi("/accounts", true, true); return useMemo(() => { if (!accounts) return; diff --git a/src/modules/activity/ActivityDescription.tsx b/src/modules/activity/ActivityDescription.tsx index 1838ba9..38165c1 100644 --- a/src/modules/activity/ActivityDescription.tsx +++ b/src/modules/activity/ActivityDescription.tsx @@ -543,6 +543,98 @@ export default function ActivityDescription({ event }: Props) {
); + /** + * Resource + */ + if (event.activity_code == "resource.group.add") + return ( +
+ Group {m.resource_name} added to resource{" "} + {m.name} +
+ ); + + if (event.activity_code == "resource.group.delete") + return ( +
+ Group {m.resource_name} removed from resource{" "} + {m.name} +
+ ); + + /** + * Networks + */ + + if (event.activity_code == "network.resource.create") + return ( +
+ Resource {m.name} created for network{" "} + {m.network_name} +
+ ); + + if (event.activity_code == "network.resource.update") + return ( +
+ Resource {m.name} updated for network{" "} + {m.network_name} +
+ ); + + if (event.activity_code == "network.resource.delete") + return ( +
+ Resource {m.name} deleted from network{" "} + {m.network_name} +
+ ); + + if (event.activity_code == "network.router.create") + return ( +
+ Routing peer created for network{" "} + {m.network_name} +
+ ); + + if (event.activity_code == "network.router.delete") + return ( +
+ Routing peer deleted from network{" "} + {m.network_name} +
+ ); + + if (event.activity_code == "network.router.update") + return ( +
+ Routing peer updated from network{" "} + {m.network_name} +
+ ); + + if (event.activity_code == "network.create") + return ( +
+ Network with name {m.name} created +
+ ); + + if (event.activity_code == "network.delete") + return ( +
+ Network with name {m.name} deleted +
+ ); + + if (event.activity_code == "network.update") + return ( +
+ Network with name {m.name} updated +
+ ); + return (
{event.activity} diff --git a/src/modules/activity/ActivityTypeIcon.tsx b/src/modules/activity/ActivityTypeIcon.tsx index a26fa2f..b21ab4c 100644 --- a/src/modules/activity/ActivityTypeIcon.tsx +++ b/src/modules/activity/ActivityTypeIcon.tsx @@ -8,6 +8,7 @@ import { Globe, HelpCircleIcon, KeyRound, + Layers3Icon, LogIn, MonitorSmartphoneIcon, NetworkIcon, @@ -89,6 +90,14 @@ export default function ActivityTypeIcon({ return ( ); + } else if (code.startsWith("resource")) { + return ( + + ); + } else if (code.startsWith("network")) { + return ( + + ); } else { return ( diff --git a/src/modules/groups/useGroupIdsToGroups.tsx b/src/modules/groups/useGroupIdsToGroups.tsx new file mode 100644 index 0000000..bd2b906 --- /dev/null +++ b/src/modules/groups/useGroupIdsToGroups.tsx @@ -0,0 +1,25 @@ +import { uniq } from "lodash"; +import { useEffect, useMemo, useState } from "react"; +import { useGroups } from "@/contexts/GroupsProvider"; +import type { Group } from "@/interfaces/Group"; + +export const useGroupIdsToGroups = (initial?: string[]) => { + const { groups, isLoading } = useGroups(); + const [initialSet, setInitialSet] = useState(false); + const [mappedGroups, setMappedGroups] = useState( + undefined, + ); + + useEffect(() => { + // Only run the mapping once when groups are loaded and initial IDs are available + if (!initialSet && !isLoading && groups && initial) { + const mapped = uniq(initial) + .map((group) => groups.find((g) => g?.id === group)) + .filter((g): g is Group => g !== undefined); + setMappedGroups(mapped); + setInitialSet(true); // Mark that we've done the initial mapping to prevent subsequent runs + } + }, [groups, initial, isLoading, initialSet]); + + return useMemo(() => mappedGroups, [mappedGroups]); +}; diff --git a/src/modules/networks/NetworkModal.tsx b/src/modules/networks/NetworkModal.tsx new file mode 100644 index 0000000..9ff51b7 --- /dev/null +++ b/src/modules/networks/NetworkModal.tsx @@ -0,0 +1,164 @@ +"use client"; + +import Button from "@components/Button"; +import HelpText from "@components/HelpText"; +import InlineLink from "@components/InlineLink"; +import { Input } from "@components/Input"; +import { Label } from "@components/Label"; +import { + Modal, + ModalClose, + ModalContent, + ModalFooter, +} from "@components/modal/Modal"; +import ModalHeader from "@components/modal/ModalHeader"; +import { notify } from "@components/Notification"; +import Paragraph from "@components/Paragraph"; +import Separator from "@components/Separator"; +import { Textarea } from "@components/Textarea"; +import { useApiCall } from "@utils/api"; +import { ExternalLinkIcon, PlusCircle } from "lucide-react"; +import React, { useState } from "react"; +import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon"; +import { Network } from "@/interfaces/Network"; + +type Props = { + open: boolean; + setOpen?: (open: boolean) => void; + network?: Network; + onCreated?: (network: Network) => void; + onUpdated?: (network: Network) => void; +}; + +export default function NetworkModal({ + open, + setOpen, + network, + onCreated, + onUpdated, +}: Readonly) { + return ( + + { + setOpen?.(false); + onCreated?.(network); + }} + onUpdated={(network) => { + setOpen?.(false); + onUpdated?.(network); + }} + key={open ? "1" : "0"} + /> + + ); +} + +type ContentProps = { + onCreated?: (network: Network) => void; + onUpdated?: (network: Network) => void; + network?: Network; +}; + +const Content = ({ network, onCreated, onUpdated }: ContentProps) => { + const [name, setName] = useState(network?.name || ""); + const [description, setDescription] = useState(network?.description || ""); + const create = useApiCall("/networks").post; + const update = useApiCall("/networks").put; + + const updateNetwork = async () => { + notify({ + title: name, + description: "Network updated successfully.", + loadingMessage: "Updating network...", + promise: update({ name, description }, `/${network?.id}`).then((n) => { + onUpdated?.(n); + }), + }); + }; + + const createNetwork = async () => { + notify({ + title: name, + description: "Network created successfully.", + loadingMessage: "Creating network...", + promise: create({ name, description }).then((n) => { + onCreated?.(n); + }), + }); + }; + + return ( + + } + title={network ? "Update Network" : "Add Network"} + description={ + network + ? network.name + : "Access resources like LANs and VPC by adding a network." + } + color={"netbird"} + /> + +
+
+ + Provide a unique name for the network. + setName(e.target.value)} + /> +
+
+ + + Write a short description to add more context to this network. + +