Compare commits

...
6 Commits
Author SHA1 Message Date
Eduard GertandMisha Bragin 25be69e7bb Add improvements to new networks features (#439)
* Fix wrong ui state for routing peer modal in networks

* Add confirmation dialog when blocking users

* Keep peer sort order when switching pages

* Update sidebar navigation order and remove deprecation notice

* Fix issue when hovering over truncated text in a group badge closes the multiple groups popover

* Update group text in network resource modal

* Update networks page text

* Fix line height

* Add search to resource table

* Switch networks flow to create first resources and then add routers

* Add enabled toggle to routing peers

* Add enabled toggle to network resources

* Add resource group modal and adjust tables

* Clarify networks

* Fix not properly aligned horizontal scroll bar

* Add option to install netbird after creating a setup key

* Fix text for install netbird modal

* Show resources count in group settings

* Fix "no results" and "no routing peers" text showing at the same time

* Fix wording

* Fix resource policy count

* Hide resource count when selection source groups

* Extend networks routing peer modal with option to create a setup key and install netbird

* Add option for horizontal stepper

* Generate setup key when installing netbird from routing peer modal

* Add confirm dialog to let the user know a one-off setup-key will be created. This avoids accidental clicking and later confusion on the setup keys page

---------

Co-authored-by: Misha Bragin <bangvalo@gmail.com>
2025-01-20 16:18:21 +01:00
Eduard Gert 43e5d5cf53 Fix activity search and allow searching for meta fields (#440) 2025-01-15 16:41:55 +01:00
Eduard Gert 18819d6fdf Add confirmation dialog when blocking users (#437) 2025-01-15 16:29:05 +01:00
Eduard Gert 158804c1ac Fix wrong ui state for routing peer modal in networks (#436) 2025-01-15 16:28:50 +01:00
Misha Bragin 14d2d68819 Update links to networks doc (#435) 2024-12-27 21:48:36 +01:00
Pascal Fischer 40902b3629 add resources to groups update operation (#434) 2024-12-27 14:20:11 +01:00
51 changed files with 1688 additions and 419 deletions

No files matched your search

+347
View File
@@ -14,6 +14,7 @@
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3",
@@ -921,6 +922,352 @@
}
}
},
"node_modules/@radix-ui/react-hover-card": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.4.tgz",
"integrity": "sha512-QSUUnRA3PQ2UhvoCv3eYvMnCAgGQW+sTu86QPuNb+ZMi+ZENd6UWpiXbcWDQ4AEaKF9KKpCHBeaJz9Rw6lRlaQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.3",
"@radix-ui/react-popper": "1.2.1",
"@radix-ui/react-portal": "1.1.3",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-arrow": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz",
"integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-context": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz",
"integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-escape-keydown": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-popper": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
"integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.0.0",
"@radix-ui/react-arrow": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0",
"@radix-ui/react-use-rect": "1.1.0",
"@radix-ui/react-use-size": "1.1.0",
"@radix-ui/rect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-portal": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
"integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-presence": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-primitive": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
"integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
"integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
"integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
"integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-rect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
"integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/rect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-size": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
"integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/rect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-id": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz",
+1
View File
@@ -19,6 +19,7 @@
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3",
+1 -4
View File
@@ -14,7 +14,6 @@ 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(
@@ -40,9 +39,7 @@ export default function NetworkRoutes() {
icon={<NetworkRoutesIcon size={13} />}
/>
</Breadcrumbs>
<h1 ref={headingRef}>
Network Routes <NetworkRoutesDeprecationInfo size={18} />
</h1>
<h1 ref={headingRef}>Network Routes</h1>
<Paragraph>
Network routes allow you to access other networks like LANs and
VPCs without installing NetBird on every resource.
+6 -3
View File
@@ -31,12 +31,15 @@ export default function Networks() {
</Breadcrumbs>
<h1 ref={headingRef}>Networks</h1>
<Paragraph>
Networks allow you to access other resources like LANs and VPCs
without installing NetBird on every device.
Networks allow you to access internal resources in LANs and VPCs without
installing NetBird on every machine.
</Paragraph>
<Paragraph>
Learn more about
<InlineLink href={"https://docs.netbird.io/how-to/networks-concept"} target={"_blank"}>
<InlineLink
href={"https://docs.netbird.io/how-to/networks"}
target={"_blank"}
>
Networks
<ExternalLinkIcon size={12} />
</InlineLink>
+1
View File
@@ -316,6 +316,7 @@ function UserInformationCard({ user }: { user: User }) {
<>
{!user.is_current && user.role != Role.Owner && (
<Card.ListItem
tooltip={false}
label={
<>
<Ban size={16} />
+25 -5
View File
@@ -30,7 +30,7 @@ import * as React from "react";
import { Fragment, useEffect, useMemo, useState } from "react";
import { useGroups } from "@/contexts/GroupsProvider";
import { useElementSize } from "@/hooks/useElementSize";
import type { Group, GroupPeer } from "@/interfaces/Group";
import type { Group, GroupPeer, GroupResource } from "@/interfaces/Group";
import { NetworkResource } from "@/interfaces/Network";
import type { Peer } from "@/interfaces/Peer";
@@ -48,6 +48,7 @@ interface MultiSelectProps {
showRoutes?: boolean;
disabledGroups?: Group[];
dataCy?: string;
showResourceCounter?: boolean;
}
export function PeerGroupSelector({
onChange,
@@ -63,6 +64,7 @@ export function PeerGroupSelector({
showRoutes = false,
disabledGroups,
dataCy = "group-selector-dropdown",
showResourceCounter = true,
}: Readonly<MultiSelectProps>) {
const { groups, dropdownOptions, setDropdownOptions, addDropdownOptions } =
useGroups();
@@ -104,19 +106,35 @@ export function PeerGroupSelector({
const option = dropdownOptions.find((option) => option.name == name);
const groupPeers: GroupPeer[] | undefined =
(group?.peers as GroupPeer[]) || [];
const groupResources: GroupResource[] | undefined =
(group?.resources as GroupResource[]) || [];
if (peer) groupPeers?.push({ id: peer?.id as string, name: peer?.name });
if (!group && !option) {
addDropdownOptions([{ name: name, peers: groupPeers }]);
addDropdownOptions([
{ name: name, peers: groupPeers, resources: groupResources },
]);
}
if (max == 1 && values.length == 1) {
onChange([{ name: name, id: group?.id, peers: groupPeers }]);
onChange([
{
name: name,
id: group?.id,
peers: groupPeers,
resources: groupResources,
},
]);
} else {
onChange((previous) => [
...previous,
{ name: name, id: group?.id, peers: groupPeers },
{
name: name,
id: group?.id,
peers: groupPeers,
resources: groupResources,
},
]);
}
@@ -394,7 +412,9 @@ export function PeerGroupSelector({
<AccessControlGroupCount group_id={option.id} />
)}
<ResourcesCounter group={option} />
{showResourceCounter && (
<ResourcesCounter group={option} />
)}
<div
className={
+9 -7
View File
@@ -166,15 +166,17 @@ export function PeerSelector({
placeholder={"Search for peers by name or ip..."}
/>
{unfilteredItems.length == 0 && (
<DropdownInfoText>
{
"Seems like you don't have any linux peers to assign as a routing peer."
}
</DropdownInfoText>
{unfilteredItems.length == 0 && !search && (
<div className={"max-w-xs mx-auto"}>
<DropdownInfoText>
{
"Seems like you don't have any Linux peers to assign as a routing peer."
}
</DropdownInfoText>
</div>
)}
{filteredItems.length == 0 && (
{filteredItems.length == 0 && search != "" && (
<DropdownInfoText>
There are no peers matching your search.
</DropdownInfoText>
+16 -35
View File
@@ -1,5 +1,3 @@
"use client";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@utils/helpers";
import * as React from "react";
@@ -15,46 +13,31 @@ const ScrollArea = React.forwardRef<
>(({ className, children, withoutViewport = false, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn(
"relative will-change-scroll webkit-scroll",
className,
"overflow-hidden",
)}
className={cn("relative overflow-hidden", className)}
{...props}
>
{withoutViewport ? (
children
) : (
<ScrollAreaViewport disableOverflowY={false}>
{children}
</ScrollAreaViewport>
<ScrollAreaViewport>{children}</ScrollAreaViewport>
)}
<ScrollBar />
<ScrollBar orientation="horizontal" />
<ScrollBar orientation="vertical" />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
type AdditionalScrollAreaViewportProps = {
disableOverflowY?: boolean;
};
const ScrollAreaViewport = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Viewport> &
AdditionalScrollAreaViewportProps
>(({ disableOverflowY = true, ...props }, ref) => {
return (
<ScrollAreaPrimitive.Viewport
ref={ref}
className="h-full w-full rounded-[inherit] will-change-scroll webkit-scroll"
{...props}
style={
disableOverflowY ? { overflowY: undefined, ...props.style } : undefined
}
/>
);
});
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<ScrollAreaPrimitive.Viewport
ref={ref}
className={cn("h-full w-full rounded-[inherit]", className)}
{...props}
/>
));
ScrollAreaViewport.displayName = ScrollAreaPrimitive.Viewport.displayName;
const ScrollBar = React.forwardRef<
@@ -63,14 +46,11 @@ const ScrollBar = React.forwardRef<
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
style={{ boxSizing: "unset", overflow: undefined }}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 border-t border-t-transparent p-[1px]",
"flex select-none touch-none transition-colors",
orientation === "vertical" && "h-full w-2.5 p-[1px]",
orientation === "horizontal" && "w-full h-2.5 p-[1px] bottom-0",
className,
)}
{...props}
@@ -79,6 +59,7 @@ const ScrollBar = React.forwardRef<
className={cn(
"relative rounded-full bg-neutral-200 dark:bg-nb-gray-800",
orientation === "vertical" && "flex-1",
orientation === "horizontal" && "h-full",
)}
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
+4 -2
View File
@@ -60,10 +60,12 @@ export default function SidebarItem({
<li className={"px-4 cursor-pointer"}>
<button
className={classNames(
"rounded-lg text-[.95rem] w-full ",
"rounded-lg text-[.87rem] w-full ",
"font-normal ",
className,
isChild ? "pl-7 pr-2 py-2 mt-1 mb-0.5" : "py-2 px-3",
isChild
? "pl-7 pr-2 py-[.45rem] mt-1 mb-0.5"
: "py-[.45rem] px-3",
isActive
? "text-gray-900 bg-gray-200 dark:text-white dark:bg-nb-gray-900"
: "text-gray-600 hover:bg-gray-200 dark:text-nb-gray-400 dark:hover:bg-nb-gray-900/50",
+27 -7
View File
@@ -4,9 +4,18 @@ import React from "react";
type Props = {
children: React.ReactNode;
className?: string;
horizontal?: boolean;
};
export default function Steps({ children, className }: Props) {
return <div className={cn("pt-4", className)}>{children}</div>;
export default function Steps({
children,
className,
horizontal = false,
}: Readonly<Props>) {
return (
<div className={cn("pt-4", horizontal && "flex", className)}>
{children}
</div>
);
}
type StepProps = {
@@ -14,21 +23,32 @@ type StepProps = {
step: number;
line?: boolean;
center?: boolean;
horizontal?: boolean;
};
const Step = ({ children, step, line = true, center = false }: StepProps) => {
const Step = ({
children,
step,
line = true,
center = false,
horizontal,
}: StepProps) => {
return (
<div
className={cn(
"flex gap-4 items-start min-w-full justify-start relative pb-6 -mx-1.5 group px-[2px]",
"flex gap-4 items-start justify-start relative pb-6 -mx-1.5 group px-[2px]",
center && "items-center",
horizontal ? "flex-col items-center" : "min-w-full",
)}
>
{line && (
<span
className={
"h-full w-[2px] bg-nb-gray-100 dark:bg-nb-gray-800 absolute left-0 ml-[18px] z-0 transition-all"
}
className={cn(
"bg-nb-gray-100 dark:bg-nb-gray-800 z-0 transition-all",
horizontal
? "w-full h-[2px] absolute mt-[16px] transform translate-x-1/2"
: "h-full w-[2px] absolute left-0 ml-[18px]",
)}
></span>
)}
+4 -12
View File
@@ -1,5 +1,6 @@
import Badge from "@components/Badge";
import TextWithTooltip from "@components/ui/TextWithTooltip";
import { NewBadge } from "@components/ui/NewBadge";
import TruncatedText from "@components/ui/TruncatedText";
import { cn } from "@utils/helpers";
import { FolderGit2, XIcon } from "lucide-react";
import * as React from "react";
@@ -37,18 +38,9 @@ export default function GroupBadge({
>
<FolderGit2 size={12} className={"shrink-0"} />
<TextWithTooltip text={group?.name || ""} maxChars={20} />
<TruncatedText text={group?.name || ""} maxChars={20} />
{children}
{isNew && showNewBadge && (
<span
className={
"text-[7px] relative top-[.25px] leading-[0] bg-green-900 border border-green-500/20 py-1.5 px-1 rounded-[3px] text-green-400"
}
>
NEW
</span>
)}
{isNew && showNewBadge && <NewBadge />}
{showX && (
<XIcon
size={12}
+16
View File
@@ -0,0 +1,16 @@
import * as React from "react";
type Props = {
text?: string;
};
export const NewBadge = ({ text = "NEW" }: Props) => {
return (
<span
className={
"text-[7px] relative top-[.25px] leading-[0] bg-green-900 border border-green-500/20 py-1.5 px-1 rounded-[3px] text-green-400"
}
>
{text}
</span>
);
};
+78
View File
@@ -0,0 +1,78 @@
import * as HoverCard from "@radix-ui/react-hover-card";
import { cn } from "@utils/helpers";
import React, { useMemo, useState } from "react";
type Props = {
text?: string;
className?: string;
maxChars?: number;
hideTooltip?: boolean;
};
export default function TruncatedText({
text,
className,
maxChars = 40,
hideTooltip = false,
}: Props) {
const charCount = useMemo(() => {
if (!text) return 0;
return text.length;
}, [text]);
const isDisabled = charCount <= maxChars || hideTooltip;
const [open, setOpen] = useState(false);
if (isDisabled) {
return (
<div
className={"w-full min-w-0 inline-block"}
style={{
maxWidth: `${maxChars - 2}ch`,
}}
>
<div className={cn(className, "truncate")}>{text}</div>
</div>
);
}
return (
<HoverCard.Root
openDelay={650}
closeDelay={100}
open={open}
onOpenChange={setOpen}
>
<HoverCard.Trigger asChild={true}>
<div
className={"w-full min-w-0 inline-block"}
style={{
maxWidth: `${maxChars - 2}ch`,
}}
>
<div className={cn(className, "truncate")}>{text}</div>
</div>
</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content
onMouseLeave={() => setOpen(false)}
onMouseEnter={() => setOpen(false)}
alignOffset={20}
sideOffset={4}
className={cn(
"z-[9999] overflow-hidden rounded-md border border-neutral-200 bg-white text-sm text-neutral-950 shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-nb-gray-930 dark:bg-nb-gray-940 dark:text-neutral-50",
className,
"px-3 py-1.5",
)}
>
<div className={"text-neutral-300 flex flex-col gap-1"}>
<div className={"max-w-xs break-all whitespace-normal text-xs"}>
{text}
</div>
</div>
</HoverCard.Content>
</HoverCard.Portal>
</HoverCard.Root>
);
}
+10 -1
View File
@@ -2,7 +2,7 @@ import useFetchApi, { useApiCall } from "@utils/api";
import { merge, sortBy, unionBy } from "lodash";
import React, { useEffect, useState } from "react";
import { useLoggedInUser } from "@/contexts/UsersProvider";
import { Group } from "@/interfaces/Group";
import {Group, GroupResource} from "@/interfaces/Group";
import { Peer } from "@/interfaces/Peer";
type Props = {
@@ -102,6 +102,13 @@ export function GroupsProviderContent({
return peer.id;
}) as string[];
let resources = group?.resources?.map((r) => {
let isString = typeof r === "string";
if (isString) return r;
let resource = r as GroupResource;
return resource.id;
}) as string[];
if (group.name === "All") return Promise.resolve(group);
const groupID =
@@ -112,6 +119,7 @@ export function GroupsProviderContent({
{
name: group.name,
peers: peers,
resources: resources,
},
`/${group.id}`,
);
@@ -119,6 +127,7 @@ export function GroupsProviderContent({
return groupRequest.post({
name: group.name,
peers: peers,
resources: resources,
});
}
};
+6 -1
View File
@@ -3,7 +3,7 @@ export interface Group {
name: string;
peers?: GroupPeer[] | string[];
peers_count?: number;
resources?: string[];
resources?: GroupResource[] | string[];
resources_count?: number;
// Frontend only
@@ -14,3 +14,8 @@ export interface GroupPeer {
id: string;
name: string;
}
export interface GroupResource {
id: string;
type: string;
}
+2
View File
@@ -16,6 +16,7 @@ export interface NetworkRouter {
peer_groups?: string[];
metric: number;
masquerade: boolean;
enabled: boolean;
}
export interface NetworkResource {
@@ -25,4 +26,5 @@ export interface NetworkResource {
address: string;
groups?: string[] | Group[];
type?: "domain" | "host" | "subnet";
enabled: boolean;
}
@@ -281,6 +281,7 @@ export function AccessControlModalContent({
onChange={setSourceGroups}
values={sourceGroups}
saveGroupAssignments={useSave}
showResourceCounter={false}
/>
</div>
<PolicyDirection
@@ -391,9 +392,7 @@ export function AccessControlModalContent({
<Paragraph className={"text-sm mt-auto"}>
Learn more about
<InlineLink
href={
"https://docs.netbird.io/how-to/manage-network-access"
}
href={"https://docs.netbird.io/how-to/manage-network-access"}
target={"_blank"}
>
Access Controls
+17
View File
@@ -37,6 +37,22 @@ const ActivityFeedColumnsTable: ColumnDef<ActivityEvent>[] = [
filterFn: "arrIncludesSomeExact",
cell: ({ row }) => <ActivityEntryRow event={row.original} />,
},
{
id: "activity_text",
accessorFn: (event) => {
try {
if (event.meta) {
return Object.keys(event.meta)
.map((key) => {
return `${event?.meta[key]}`;
})
.join(" ");
}
} catch (error) {
return "";
}
},
},
{
accessorKey: "timestamp",
id: "timestamp",
@@ -103,6 +119,7 @@ export default function ActivityTable({
columnVisibility={{
timestamp: false,
name: false,
activity_text: false,
initiator_email: false,
}}
getStartedCard={
@@ -124,6 +124,7 @@ export const AssignGroupToPeerModalContent = ({
} as GroupPeer;
}),
peers_count: selectedPeers.length,
resources: group.resources,
keepClientState: true,
});
return;
@@ -138,6 +139,7 @@ export const AssignGroupToPeerModalContent = ({
{
name: group.name,
peers: selectedPeers.map((peer) => peer.id),
resources: group.resources,
},
"/" + group?.id,
);
@@ -146,6 +148,7 @@ export const AssignGroupToPeerModalContent = ({
groupRequest.post({
name: group.name,
peers: selectedPeers.map((peer) => peer.id),
resources: group.resources,
});
}
notify({
+3
View File
@@ -80,6 +80,7 @@ export default function useGroupHelper({ initial = [], peer }: Props) {
return groupRequest.put(
{
name: g.name,
resources: g.resources,
peers: newPeerGroups
? newPeerGroups.map((p) => {
const groupPeer = p as GroupPeer;
@@ -112,6 +113,7 @@ export default function useGroupHelper({ initial = [], peer }: Props) {
{
name: selectedGroup.name,
peers: peers,
resources: selectedGroup.resources,
},
`/${selectedGroup.id}`,
);
@@ -122,6 +124,7 @@ export default function useGroupHelper({ initial = [], peer }: Props) {
.post({
name: selectedGroup.name,
peers: groupPeers || [],
resources: selectedGroup.resources,
})
.then((group) => {
setSelectedGroups((prev) => {
Loaded 20 of 51 files, more files were not shown because too many files have changed in this diff. Show more