Compare commits

..
Author SHA1 Message Date
Eduard Gert 41dfccca76 Add description for network resource 2025-01-22 12:12:09 +01:00
86 changed files with 741 additions and 2276 deletions

No files matched your search

-64
View File
@@ -1,64 +0,0 @@
## Contributor License Agreement
This Contributor License Agreement (referred to as the "Agreement") is entered into by the individual
submitting this Agreement and NetBird GmbH, c/o Max-Beer-Straße 2-4 Münzstraße 12 10178 Berlin, Germany,
referred to as "NetBird" (collectively, the "Parties"). The Agreement outlines the terms and conditions
under which NetBird may utilize software contributions provided by the Contributor for inclusion in
its software development projects. By submitting this Agreement, the Contributor confirms their acceptance
of the terms and conditions outlined below. The Contributor further represents that they are authorized to
complete this process as described herein.
## 1 Preamble
In order to clarify the IP Rights situation with regard to Contributions from any person or entity, NetBird
must have a contributor license agreement on file to be signed by each Contributor, containing the license
terms below. This license serves as protection for both the Contributor as well as NetBird and its software users;
it does not change Contributor’s rights to use his/her own Contributions for any other purpose.
## 2 Definitions
2.1 “IP Rights” shall mean all industrial and intellectual property rights, whether registered or not registered, whether created by Contributor or acquired by Contributor from third parties, and similar rights, including (but not limited to) semiconductor property rights, design rights, copyrights (including in the form of database rights and rights to software), all neighbouring rights (Leistungsschutzrechte), trademarks, service marks, titles, internet domain names, trade names and other labelling rights, rights deriving from corresponding applications and registrations of such rights as well as any licenses (Nutzungsrechte) under and entitlements to any such intellectual and industrial property rights.
2.2 "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is or previously has been intentionally Submitted by Contributor to NetBird for inclusion in, or documentation of any Work.
2.3 "Contributor" shall mean the copyright owner or legal entity authorized by the copyright owner that is concluding this Agreement with NetBird. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
2.4 "Submitted" shall mean any form of electronic, verbal, or written communication sent to NetBird or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, NetBird for the purpose of discussing and improving the Work, but excluding communication that is marked or otherwise designated in writing by Contributor as "Not a Contribution".
2.5 "Work" means any of the products owned or managed by NetBird, in particular, but not exclusively, software.
## 3 Licenses
3.1 Subject to the terms and conditions of this agreement, Contributor hereby grants to NetBird and to recipients of software distributed by NetBird a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to reproduce by any means and in any form, in whole or in part, permanently or temporarily, the Contributions (including loading, displaying, executing, transmitting or storing works for the purpose of executing and processing data or transferring them to video, audio and other data carriers), including the right to distribute, display and present such Contributions and make them available to the public (e.g. via the internet) and to transmit and display such Contributions by any means. The license also includes the right to modify, translate, adapt, edit and otherwise alter the Contributions and to use these results in the same manner as the original Contributions and derivative works. Except for licenses in patents acc. to Sec. 3, such license refers to any IP Rights in the Contributions and derivative works. The Contributor acknowledges that NetBird is not required to credit them by name for their Contribution and agrees to waive any moral rights associated with their Contribution in relation to NetBird or its sublicensees.
3.2 Subject to the terms and conditions of this agreement, Contributor hereby grants to NetBird and to recipients of software distributed by NetBird a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license in the Contributions to make, have made, use, sell, offer to sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by the Contributor which are necessarily infringed by Contributor‘s Contribution(s) alone or by combination of Contributor’s Contribution(s) with the Work to which such Contribution(s) was Submitted.
3.3 NetBird hereby accepts such licenses.
## 4 Contributor’s Representations
4.1 Contributor represents that Contributor is legally entitled to grant the above license. If Contributor’s employer has IP Rights to Contributor’s Contributions, Contributor represent that he/she has received permission to make Contributions on behalf of such employer, that such employer has waived such IP Rights to the Contributions of Contributor to NetBird, or that such employer has executed a separate contributor license agreement with NetBird.
4.2 Contributor represents that any Contribution is his/her original creation.
4.3 Contributor represents to his/her best knowledge that any Contribution does not violate any third party IP Rights.
4.4 Contributor represents that any Contribution submission includes complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which Contributor is personally aware and which are associated with any part of the Contribution.
4.5 The Contributor represents that their Contribution does not include any work distributed under a copyleft license.
## 5 Information obligation
Contributor agrees to notify NetBird of any facts or circumstances of which Contributor become aware that would make these representations inaccurate in any respect.
## 6 Submission of Third-Party works
Should Contributor wish to submit work that is not Contributor’s original creation, Contributor may submit it to NetBird separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which Contributor are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
## 7 No Consideration
Unless compensation is mandatory under statutory law, no compensation for any license under this agreement shall be payable.
## 8 Final Provisions
8.1 Laws. This Agreement is governed by the laws of the Federal Republic of Germany.
8.2 Venue. Place of jurisdiction shall, to the extent legally permissible, be Berlin, Germany.
8.3 Severability. If any provision in this agreement is unlawful, invalid or ineffective, it shall not affect the enforceability or effectiveness of the remainder of this agreement. The parties agree to replace any unlawful, invalid or ineffective provision with a provision that comes as close as possible to the commercial intent and purpose of the original provision. This section also applies accordingly to any gaps in the contract.
8.4 Variations. Any variations, amendments or supplements to this Agreement must be in writing. This also applies to any variation of this Section 8.4.
+63 -87
View File
@@ -23,13 +23,13 @@ import Separator from "@components/Separator";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import LoginExpiredBadge from "@components/ui/LoginExpiredBadge";
import TextWithTooltip from "@components/ui/TextWithTooltip";
import { getOperatingSystem } from "@hooks/useOperatingSystem";
import useRedirect from "@hooks/useRedirect";
import { IconCloudLock, IconInfoCircle } from "@tabler/icons-react";
import useFetchApi from "@utils/api";
import { cn } from "@utils/helpers";
import dayjs from "dayjs";
import { isEmpty, trim } from "lodash";
import {
Barcode,
Cpu,
FlagIcon,
Globe,
@@ -40,7 +40,6 @@ import {
NetworkIcon,
PencilIcon,
TerminalSquare,
TimerResetIcon,
} from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { toASCII } from "punycode";
@@ -56,11 +55,11 @@ import PeerProvider, { usePeer } from "@/contexts/PeerProvider";
import RoutesProvider from "@/contexts/RoutesProvider";
import { useLoggedInUser } from "@/contexts/UsersProvider";
import { useHasChanges } from "@/hooks/useHasChanges";
import { OperatingSystem } from "@/interfaces/OperatingSystem";
import type { Peer } from "@/interfaces/Peer";
import PageContainer from "@/layouts/PageContainer";
import useGroupHelper from "@/modules/groups/useGroupHelper";
import { AccessiblePeersSection } from "@/modules/peer/AccessiblePeersSection";
import { PeerExpirationToggle } from "@/modules/peer/PeerExpirationToggle";
import { PeerNetworkRoutesSection } from "@/modules/peer/PeerNetworkRoutesSection";
export default function PeerPage() {
@@ -70,16 +69,9 @@ export default function PeerPage() {
useRedirect("/peers", false, !peerId);
const peerKey = useMemo(() => {
let id = peer?.id ?? "";
let ssh = peer?.ssh_enabled ? "1" : "0";
let expiration = peer?.login_expiration_enabled ? "1" : "0";
return `${id}-${ssh}-${expiration}`;
}, [peer]);
return peer && !isLoading ? (
<PeerProvider peer={peer} key={peerId}>
<PeerOverview key={peerKey} />
<PeerOverview />
</PeerProvider>
) : (
<FullScreenLoading />
@@ -96,15 +88,20 @@ function PeerOverview() {
const [loginExpiration, setLoginExpiration] = useState(
peer.login_expiration_enabled,
);
const [inactivityExpiration, setInactivityExpiration] = useState(
peer.inactivity_expiration_enabled,
);
const [selectedGroups, setSelectedGroups, { getAllGroupCalls }] =
useGroupHelper({
initial: peerGroups,
peer,
});
/**
* Check the operating system of the peer, if it is linux, then show the routes table, otherwise hide it.
*/
const isLinux = useMemo(() => {
const operatingSystem = getOperatingSystem(peer.os);
return operatingSystem == OperatingSystem.LINUX;
}, [peer.os]);
/**
* Detect if there are changes in the peer information, if there are changes, then enable the save button.
*/
@@ -113,16 +110,10 @@ function PeerOverview() {
ssh,
selectedGroups,
loginExpiration,
inactivityExpiration,
]);
const updatePeer = async () => {
const updateRequest = update({
name,
ssh,
loginExpiration,
inactivityExpiration,
});
const updateRequest = update(name, ssh, loginExpiration);
const groupCalls = getAllGroupCalls();
const batchCall = groupCalls
? [...groupCalls, updateRequest]
@@ -133,19 +124,13 @@ function PeerOverview() {
promise: Promise.all(batchCall).then(() => {
mutate("/peers/" + peer.id);
mutate("/groups");
updateHasChangedRef([
name,
ssh,
selectedGroups,
loginExpiration,
inactivityExpiration,
]);
updateHasChangedRef([name, ssh, selectedGroups, loginExpiration]);
}),
loadingMessage: "Saving the peer...",
});
};
const { isUser, isOwnerOrAdmin } = useLoggedInUser();
const { isUser } = useLoggedInUser();
return (
<PageContainer>
@@ -227,43 +212,53 @@ function PeerOverview() {
<div className={"flex gap-10 w-full mt-5 max-w-6xl"}>
<PeerInformationCard peer={peer} />
<div className={"flex flex-col gap-6 w-1/2 transition-all"}>
<div>
<PeerExpirationToggle
peer={peer}
value={loginExpiration}
icon={<TimerResetIcon size={16} />}
onChange={(state) => {
setLoginExpiration(state);
!state && setInactivityExpiration(false);
}}
/>
{isOwnerOrAdmin && !!peer?.user_id && (
<div className={"flex flex-col gap-6 w-1/2"}>
<FullTooltip
content={
<div
className={cn(
"border border-nb-gray-900 border-t-0 rounded-b-md bg-nb-gray-940 px-[1.28rem] pt-3 pb-5 flex flex-col gap-4 mx-[0.25rem]",
!loginExpiration
? "opacity-50 pointer-events-none"
: "bg-nb-gray-930/80",
)}
className={
"flex gap-2 items-center !text-nb-gray-300 text-xs"
}
>
<PeerExpirationToggle
peer={peer}
variant={"blank"}
value={inactivityExpiration}
onChange={setInactivityExpiration}
title={"Require login after disconnect"}
description={
"Enable to require authentication after users disconnect from management for 10 minutes."
}
className={
!loginExpiration ? "opacity-40 pointer-events-none" : ""
}
/>
{!peer.user_id ? (
<>
<>
<IconInfoCircle size={14} />
<span>
Login expiration is disabled for all peers added
with an setup-key.
</span>
</>
</>
) : (
<>
<LockIcon size={14} />
<span>
{`You don't have the required permissions to update this
setting.`}
</span>
</>
)}
</div>
)}
</div>
}
className={"w-full block"}
disabled={!!peer.user_id && !isUser}
>
<FancyToggleSwitch
disabled={!peer.user_id || isUser}
value={loginExpiration}
onChange={setLoginExpiration}
label={
<>
<IconCloudLock size={16} />
Login Expiration
</>
}
helpText={
"Enable to require SSO login peers to re-authenticate when their login expires."
}
/>
</FullTooltip>
<FullTooltip
content={
<div
@@ -321,7 +316,7 @@ function PeerOverview() {
</div>
</div>
{!isUser ? (
{isLinux && !isUser ? (
<>
<Separator />
<PeerNetworkRoutesSection peer={peer} />
@@ -339,7 +334,7 @@ function PeerOverview() {
);
}
function PeerInformationCard({ peer }: Readonly<{ peer: Peer }>) {
function PeerInformationCard({ peer }: { peer: Peer }) {
const { isLoading, getRegionByPeer } = useCountries();
const countryText = useMemo(() => {
@@ -375,20 +370,14 @@ function PeerInformationCard({ peer }: Readonly<{ peer: Peer }>) {
<Card.ListItem
copy
copyText={"DNS label"}
copyText={"Domain name"}
label={
<>
<Globe size={16} />
Domain Name
</>
}
className={
peer?.extra_dns_labels && peer.extra_dns_labels.length > 0
? "items-start"
: ""
}
value={peer.dns_label}
extraText={peer?.extra_dns_labels}
/>
<Card.ListItem
@@ -440,19 +429,6 @@ function PeerInformationCard({ peer }: Readonly<{ peer: Peer }>) {
}
value={peer.os}
/>
{peer.serial_number && peer.serial_number !== "" && (
<Card.ListItem
label={
<>
<Barcode size={16} />
Serial Number
</>
}
value={peer.serial_number}
/>
)}
<Card.ListItem
label={
<>
@@ -499,7 +475,7 @@ interface ModalProps {
peer: Peer;
initialName: string;
}
function EditNameModal({ onSuccess, peer, initialName }: Readonly<ModalProps>) {
function EditNameModal({ onSuccess, peer, initialName }: ModalProps) {
const [name, setName] = useState(initialName);
const isDisabled = useMemo(() => {
+6 -11
View File
@@ -40,7 +40,6 @@ export default function UserPage() {
const { data: users, isLoading } = useFetchApi<User[]>(
`/users?service_user=${isServiceUser}`,
);
const { isOwnerOrAdmin } = useLoggedInUser();
const user = useMemo(() => {
return users?.find((u) => u.id === userId);
@@ -50,15 +49,11 @@ export default function UserPage() {
const userGroups = useGroupIdsToGroups(user?.auto_groups);
if (!isOwnerOrAdmin && user && !isLoading) {
return <UserOverview user={user} initialGroups={[]} />;
}
if (isOwnerOrAdmin && user && !isLoading && userGroups) {
return <UserOverview user={user} initialGroups={userGroups} />;
}
return <FullScreenLoading />;
return !isLoading && user && userGroups !== undefined ? (
<UserOverview user={user} initialGroups={userGroups} />
) : (
<FullScreenLoading />
);
}
type Props = {
@@ -200,7 +195,7 @@ function UserOverview({ user, initialGroups }: Readonly<Props>) {
<div className={"flex gap-10 w-full mt-8 max-w-6xl items-start"}>
<UserInformationCard user={user} />
<div className={"flex flex-col gap-8 w-1/2 "}>
{!user.is_service_user && isOwnerOrAdmin && (
{!user.is_service_user && (
<div>
<Label>Auto-assigned groups</Label>
<HelpText>
+1 -1
View File
@@ -12,7 +12,7 @@ export default function CircleIcon({
size = 11,
inactiveDot = "gray",
className,
}: Readonly<Props>) {
}: Props) {
return (
<span
style={{ width: size + "px", height: size + "px" }}
-39
View File
@@ -1,39 +0,0 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function EntraIcon(props: Readonly<IconProps>) {
return (
<svg
width="231"
height="231"
viewBox="0 0 231 231"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...iconProperties(props)}
>
<path
d="M48.7923 180.077C53.7717 183.183 62.0492 186.635 70.8015 186.635C78.771 186.635 86.1758 184.325 92.3102 180.385C92.3102 180.385 92.323 180.385 92.3358 180.373L115.5 165.896V218.167C111.83 218.167 108.134 217.166 104.925 215.164L48.7923 180.077Z"
fill="#225086"
/>
<path
d="M100.78 19.3398L4.53017 127.91C-2.90033 136.303 -0.962501 148.982 8.67533 155.001C8.67533 155.001 44.3007 177.267 48.7923 180.077C53.7717 183.183 62.0492 186.635 70.8015 186.635C78.771 186.635 86.1758 184.325 92.3102 180.385C92.3102 180.385 92.323 180.385 92.3358 180.373L115.5 165.896L59.4953 130.887L115.513 67.6958V12.8333C110.072 12.8333 104.63 15.0022 100.78 19.3398Z"
fill="#66DDFF"
/>
<path
d="M59.4953 130.887L60.1627 131.298L115.5 165.896H115.513V67.7087L115.5 67.6958L59.4953 130.887Z"
fill="#CBF8FF"
/>
<path
d="M222.325 155.001C231.963 148.982 233.9 136.303 226.47 127.91L163.317 56.672C158.222 54.2978 152.511 52.9375 146.467 52.9375C134.596 52.9375 123.983 58.058 116.925 66.1045L115.526 67.683L171.53 130.874L115.513 165.884V218.154C119.196 218.154 122.866 217.153 126.075 215.151L222.325 154.988V155.001Z"
fill="#074793"
/>
<path
d="M115.513 12.8333V67.6958L116.912 66.1173C123.97 58.0708 134.583 52.9503 146.454 52.9503C152.511 52.9503 158.209 54.3235 163.304 56.6848L130.207 19.3527C126.37 15.015 120.929 12.8462 115.5 12.8462L115.513 12.8333Z"
fill="#0294E4"
/>
<path
d="M171.518 130.887L115.513 67.7087V165.884L171.518 130.887Z"
fill="#96BCC2"
/>
</svg>
);
}
-31
View File
@@ -1,31 +0,0 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function GoogleIcon(props: Readonly<IconProps>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
{...iconProperties(props)}
>
<path
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
<path d="M1 1h22v22H1z" fill="none" />
</svg>
);
}
-36
View File
@@ -1,36 +0,0 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function JWTIcon(props: Readonly<IconProps>) {
return (
<svg
height="2500"
viewBox=".4 .3 99.7 100"
width="2500"
xmlns="http://www.w3.org/2000/svg"
{...iconProperties(props)}
>
<g fill="none">
<path
d="m57.8 27.2-.1-26.9h-15l.1 26.9 7.5 10.3zm-15 46.1v27h15v-27l-7.5-10.3z"
fill="#fff"
/>
<path
d="m57.8 73.3 15.8 21.8 12.1-8.8-15.8-21.8-12.1-3.9zm-15-46.1-15.9-21.8-12.1 8.8 15.8 21.8 12.2 3.9z"
fill="#00f2e6"
/>
<path
d="m30.6 36-25.6-8.3-4.6 14.2 25.6 8.4 12.1-4zm31.8 18.2 7.5 10.3 25.6 8.3 4.6-14.2-25.6-8.3z"
fill="#00b9f1"
/>
<path
d="m74.5 50.3 25.6-8.4-4.6-14.2-25.6 8.3-7.5 10.3zm-48.5 0-25.6 8.3 4.6 14.2 25.6-8.3 7.5-10.3z"
fill="#d63aff"
/>
<path
d="m30.6 64.5-15.8 21.8 12.1 8.8 15.9-21.8v-12.7zm39.3-28.5 15.8-21.8-12.1-8.8-15.8 21.8v12.7z"
fill="#fb015b"
/>
</g>
</svg>
);
}
-18
View File
@@ -1,18 +0,0 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function OktaIcon(props: Readonly<IconProps>) {
return (
<svg
width="215"
height="215"
viewBox="0 0 215 215"
xmlns="http://www.w3.org/2000/svg"
{...iconProperties(props)}
>
<path
d="M118.287 1.49306L113.865 55.9597C111.773 55.7208 109.681 55.6014 107.53 55.6014C104.841 55.6014 102.211 55.7806 99.6412 56.1986L97.1312 29.8014C97.0714 28.9653 97.7288 28.2486 98.5655 28.2486H103.048L100.896 1.55278C100.836 0.716667 101.494 0 102.271 0H116.913C117.749 0 118.407 0.716667 118.287 1.55278V1.49306ZM81.3538 4.18056C81.1147 3.40417 80.278 2.92639 79.5011 3.225L65.7557 8.24167C64.9788 8.54028 64.6202 9.43611 64.9788 10.1528L76.1544 34.5194L71.9112 36.0722C71.1343 36.3708 70.7758 37.2667 71.1343 37.9833L82.549 61.9319C86.6727 59.6625 91.1549 57.9306 95.8761 56.8556L81.4135 4.18056H81.3538ZM47.6475 19.35L79.2621 63.9028C75.258 66.5306 71.6124 69.6361 68.5048 73.2194L49.56 54.5861C48.9623 53.9889 49.0221 53.0333 49.6197 52.4958L53.086 49.6292L34.2607 30.5778C33.663 29.9806 33.7228 29.025 34.3802 28.4875L45.5558 19.1111C46.2132 18.5736 47.1097 18.6931 47.5878 19.35H47.6475ZM21.1129 45.15C20.4555 44.6722 19.4993 44.9111 19.0809 45.6278L11.7899 58.2889C11.3715 59.0056 11.6703 59.9014 12.3875 60.2597L36.6512 71.7264L34.3802 75.6083C33.9619 76.325 34.2607 77.2806 35.0376 77.5792L59.1818 88.6278C60.9149 84.1486 63.2456 79.9681 66.1143 76.2056L21.1129 45.15ZM3.24377 79.55C3.3633 78.7139 4.19998 78.2361 4.97689 78.4153L57.867 92.2111C56.4924 96.6903 55.7155 101.408 55.596 106.306L29.1211 104.156C28.2844 104.096 27.6868 103.319 27.8661 102.483L28.643 98.0639L1.92899 95.5556C1.09231 95.4958 0.554444 94.7194 0.67397 93.8833L3.18401 79.4903L3.24377 79.55ZM1.2716 115.264C0.434919 115.324 -0.102947 116.1 0.0165789 116.936L2.58638 131.329C2.70591 132.165 3.54259 132.643 4.3195 132.464L30.2566 125.715L31.0335 130.135C31.153 130.971 31.9897 131.449 32.7666 131.269L58.3451 124.222C56.851 119.803 55.8948 115.085 55.6558 110.247L1.21184 115.264H1.2716ZM9.75792 153.128C9.33958 152.411 9.63839 151.515 10.3555 151.157L59.7196 127.746C61.5723 132.165 64.0823 136.286 67.0705 139.989L45.4363 155.397C44.7789 155.875 43.8227 155.696 43.4044 154.979L41.1334 151.037L19.0809 16Line truncated
fill="white"
/>
</svg>
);
}
+20 -60
View File
@@ -7,7 +7,6 @@ import React from "react";
interface Props extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
function Card({ children, className, ...props }: Props) {
return (
<div
@@ -33,7 +32,6 @@ type CardListItemProps = {
copy?: boolean;
copyText?: string;
tooltip?: boolean;
extraText?: string[];
};
function CardListItem({
@@ -43,8 +41,9 @@ function CardListItem({
copy = false,
copyText,
tooltip = true,
extraText = [],
}: CardListItemProps) {
const [, copyToClipBoard] = useCopyToClipboard(value as string);
return (
<li
className={cn(
@@ -53,68 +52,29 @@ function CardListItem({
)}
>
<div className={"flex gap-2.5 items-center text-sm"}>{label}</div>
<div className={"flex flex-col gap-2"}>
<CardTextItem
label={label}
value={value}
copy={copy}
copyText={copyText}
tooltip={tooltip}
/>
{extraText?.map((extraLabel, index) => (
<CardTextItem
key={index}
label={label}
value={extraLabel}
copy={copy}
copyText={copyText}
tooltip={tooltip}
/>
))}
<div
className={cn(
"text-right text-nb-gray-400 text-sm flex items-center gap-2",
copy && "cursor-pointer hover:text-nb-gray-300 transition-all",
)}
onClick={() =>
copy &&
copyToClipBoard(
`${copyText ? copyText : label} has been copied to clipboard.`,
)
}
>
{tooltip ? (
<TextWithTooltip text={value as string} maxChars={40} />
) : (
value
)}
{copy && <Copy size={13} className={"shrink-0"} />}
</div>
</li>
);
}
type CardTextItemProps = {
label: React.ReactNode;
value: React.ReactNode;
copy?: boolean;
copyText?: string;
tooltip?: boolean;
};
const CardTextItem = ({
label,
value,
copy = false,
copyText,
tooltip = true,
}: CardTextItemProps) => {
const [, copyToClipBoard] = useCopyToClipboard(value as string);
return (
<div
className={cn(
"text-right text-nb-gray-400 text-sm flex items-center gap-2",
copy && "cursor-pointer hover:text-nb-gray-300 transition-all",
)}
onClick={() =>
copy &&
copyToClipBoard(
`${copyText ? copyText : label} has been copied to clipboard.`,
)
}
>
{tooltip ? (
<TextWithTooltip text={value as string} maxChars={40} />
) : (
value
)}
{copy && <Copy size={13} className={"shrink-0"} />}
</div>
);
};
Card.List = CardList;
Card.ListItem = CardListItem;
+9 -23
View File
@@ -6,18 +6,9 @@ import useCopyToClipboard from "@/hooks/useCopyToClipboard";
type Props = {
children: React.ReactNode;
message?: string;
iconAlignment?: "left" | "right";
className?: string;
alwaysShowIcon?: boolean;
};
export default function CopyToClipboardText({
children,
message,
iconAlignment = "right",
className,
alwaysShowIcon = false,
}: Props) {
export default function CopyToClipboardText({ children, message }: Props) {
const [wrapper, copyToClipboard, copied] = useCopyToClipboard();
return (
@@ -25,7 +16,6 @@ export default function CopyToClipboardText({
className={cn(
"flex gap-2 items-center group cursor-pointer transition-all hover:underline underline-offset-4 decoration-dashed decoration-nb-gray-600",
!copied && "hover:opacity-90",
className,
)}
onClick={(e) => {
e.stopPropagation();
@@ -38,21 +28,17 @@ export default function CopyToClipboardText({
{copied ? (
<CheckIcon
className={cn(
"text-nb-gray-100 group-hover:opacity-100 shrink-0",
iconAlignment === "left" ? "order-first" : "order-last",
!alwaysShowIcon && "opacity-0",
)}
size={11}
className={
"text-nb-gray-100 opacity-0 group-hover:opacity-100 shrink-0"
}
size={12}
/>
) : (
<CopyIcon
className={cn(
"text-nb-gray-100 group-hover:opacity-100 shrink-0",
iconAlignment === "left" ? "order-first" : "order-last",
!alwaysShowIcon && "opacity-0",
)}
size={11}
className={
"text-nb-gray-100 opacity-0 group-hover:opacity-100 shrink-0"
}
size={12}
/>
)}
</div>
+2 -6
View File
@@ -1,15 +1,11 @@
import { cn } from "@utils/helpers";
import * as React from "react";
type Props = {
children: React.ReactNode;
className?: string;
};
export const DropdownInfoText = ({ children, className }: Props) => {
export const DropdownInfoText = ({ children }: Props) => {
return (
<div className={cn("text-center pt-2 mb-6 text-nb-gray-400", className)}>
{children}
</div>
<div className={"text-center pt-2 mb-6 text-nb-gray-400"}>{children}</div>
);
};
+14 -70
View File
@@ -2,51 +2,16 @@ import HelpText from "@components/HelpText";
import { Label } from "@components/Label";
import { ToggleSwitch } from "@components/ToggleSwitch";
import { cn } from "@utils/helpers";
import { cva, VariantProps } from "class-variance-authority";
import React from "react";
export const fancyToggleSwitchVariants = cva([], {
variants: {
variant: {
default: ["px-6 py-4 border rounded-md"],
blank: null,
},
state: {
true: null,
false: null,
},
},
compoundVariants: [
{
variant: "default",
state: true,
className: ["border-nb-gray-800 bg-nb-gray-900/70"],
},
{
variant: "default",
state: false,
className: [
"border-nb-gray-910 bg-nb-gray-900/30 hover:bg-nb-gray-900/40",
],
},
],
});
export type FancyToggleSwitchVariants = VariantProps<
typeof fancyToggleSwitchVariants
>;
interface Props extends FancyToggleSwitchVariants {
type Props = {
value: boolean;
onChange: (value: boolean) => void;
helpText?: React.ReactNode;
label?: React.ReactNode;
children?: React.ReactNode;
disabled?: boolean;
dataCy?: string;
className?: string;
}
};
export default function FancyToggleSwitch({
value,
onChange,
@@ -54,49 +19,28 @@ export default function FancyToggleSwitch({
label,
children,
disabled = false,
dataCy,
className,
variant = "default",
}: Readonly<Props>) {
const handleToggle = () => {
if (disabled) return;
onChange(!value);
};
const handleKeyDown = (event: React.KeyboardEvent) => {
if (disabled) return;
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
handleToggle();
}
};
}: Props) {
return (
<div
onClick={handleToggle}
onKeyDown={handleKeyDown}
tabIndex={-1}
role={"switch"}
aria-checked={value}
onClick={() => {
if (disabled) return;
onChange(!value);
}}
className={cn(
"cursor-pointer transition-all duration-300 relative z-[1]",
"inline-block text-left w-full",
"px-5 py-3.5 border rounded-md cursor-pointer transition-all duration-300 relative z-[1]",
value
? "border-nb-gray-800 bg-nb-gray-900/70"
: "border-nb-gray-800 bg-nb-gray-900/30 hover:bg-nb-gray-900/40",
disabled && "opacity-50 pointer-events-none",
fancyToggleSwitchVariants({ variant, state: value }),
className,
)}
>
<div className={"flex justify-between gap-10"}>
<div className={"flex justify-between gap-10 "}>
<div className={"max-w-sm"}>
<Label>{label}</Label>
<HelpText margin={false}>{helpText}</HelpText>
</div>
<div className={"mt-2 pr-1"}>
<ToggleSwitch
checked={value}
onCheckedChange={onChange}
dataCy={dataCy}
/>
<div className={"mt-2"}>
<ToggleSwitch checked={value} onCheckedChange={onChange} />
</div>
</div>
<div>{children && value ? children : null}</div>
+2 -10
View File
@@ -22,8 +22,6 @@ type Props = {
keepOpen?: boolean;
customOpen?: boolean;
customOnOpenChange?: React.Dispatch<React.SetStateAction<boolean>>;
delayDuration?: number;
skipDelayDuration?: number;
} & TooltipProps;
export default function FullTooltip({
children,
@@ -39,8 +37,6 @@ export default function FullTooltip({
keepOpen = false,
customOpen,
customOnOpenChange,
delayDuration = 1,
skipDelayDuration = 300,
}: Props) {
const [open, setOpen] = useState(!!keepOpen);
@@ -50,13 +46,9 @@ export default function FullTooltip({
};
return !disabled ? (
<TooltipProvider
disableHoverableContent={!interactive}
delayDuration={delayDuration}
skipDelayDuration={skipDelayDuration}
>
<TooltipProvider disableHoverableContent={!interactive}>
<Tooltip
delayDuration={delayDuration}
delayDuration={1}
open={customOpen || open}
onOpenChange={customOnOpenChange || handleOpen}
>
File diff suppressed because it is too large. Load diff
+26 -66
View File
@@ -1,26 +1,31 @@
import { DropdownInfoText } from "@components/DropdownInfoText";
import { DropdownInput } from "@components/DropdownInput";
import FullTooltip from "@components/FullTooltip";
import { Popover, PopoverContent, PopoverTrigger } from "@components/Popover";
import TextWithTooltip from "@components/ui/TextWithTooltip";
import { VirtualScrollAreaList } from "@components/VirtualScrollAreaList";
import { getOperatingSystem } from "@hooks/useOperatingSystem";
import { useSearch } from "@hooks/useSearch";
import useFetchApi from "@utils/api";
import { cn } from "@utils/helpers";
import { isRoutingPeerSupported } from "@utils/version";
import { sortBy, unionBy } from "lodash";
import { ArrowUpCircleIcon, ChevronsUpDown, MapPin } from "lucide-react";
import { ChevronsUpDown, MapPin } from "lucide-react";
import * as React from "react";
import { memo, useEffect, useState } from "react";
import { FcLinux } from "react-icons/fc";
import { useElementSize } from "@/hooks/useElementSize";
import { getOperatingSystem } from "@/hooks/useOperatingSystem";
import { OperatingSystem } from "@/interfaces/OperatingSystem";
import { Peer } from "@/interfaces/Peer";
import { OSLogo } from "@/modules/peers/PeerOSCell";
const MapPinIcon = memo(() => <MapPin size={12} />);
MapPinIcon.displayName = "MapPinIcon";
const LinuxIcon = memo(() => (
<span className={"grayscale brightness-[100%] contrast-[40%]"}>
<FcLinux className={"text-white text-lg min-w-[20px] brightness-150"} />
</span>
));
LinuxIcon.displayName = "LinuxIcon";
interface MultiSelectProps {
value?: Peer;
onChange: React.Dispatch<React.SetStateAction<Peer | undefined>>;
@@ -58,6 +63,11 @@ export function PeerSelector({
// Sort
let options = sortBy([...peers], "name") as Peer[];
// Filter out peers that are not linux
options = options.filter((peer) => {
return getOperatingSystem(peer.os) === OperatingSystem.LINUX;
});
// Filter out excluded peers
if (excludedPeers) {
options = options.filter((peer) => {
@@ -118,7 +128,8 @@ export function PeerSelector({
}
>
<div className={"flex items-center gap-2.5 text-sm"}>
<TextWithTooltip text={value.name} maxChars={22} />
<LinuxIcon />
<TextWithTooltip text={value.name} maxChars={20} />
</div>
<div
@@ -140,7 +151,7 @@ export function PeerSelector({
</PopoverTrigger>
<PopoverContent
hideWhenDetached={false}
className="w-full p-0 shadow-sm shadow-nb-gray-950"
className="w-full p-0 shadow-sm shadow-nb-gray-950"
style={{
width: width,
}}
@@ -158,7 +169,9 @@ export function PeerSelector({
{unfilteredItems.length == 0 && !search && (
<div className={"max-w-xs mx-auto"}>
<DropdownInfoText>
{"No peers available to select."}
{
"Seems like you don't have any Linux peers to assign as a routing peer."
}
</DropdownInfoText>
</div>
)}
@@ -172,35 +185,10 @@ export function PeerSelector({
{filteredItems.length > 0 && (
<VirtualScrollAreaList
items={filteredItems}
onSelect={(item) => {
const isSupported = isRoutingPeerSupported(
item.version,
item.os,
);
if (!isSupported) return;
togglePeer(item);
}}
onSelect={togglePeer}
renderItem={(option) => {
const os = getOperatingSystem(option.os);
const isSupported = isRoutingPeerSupported(
option.version,
option.os,
);
return (
<FullTooltip
disabled={isSupported}
interactive={false}
delayDuration={200}
skipDelayDuration={350}
className={"w-full flex items-center justify-between"}
content={
<div className={"max-w-[240px] text-xs"}>
Please update NetBird to at least{" "}
<span className={"text-netbird"}>v0.36.6</span> or later
to use this peer as a routing peer.
</div>
}
>
<>
<div
className={cn(
"flex items-center gap-2.5 text-sm",
@@ -209,35 +197,8 @@ export function PeerSelector({
: "text-nb-gray-300",
)}
>
<div
className={cn(
"flex items-center justify-center grayscale brightness-[100%] contrast-[40%]",
"w-4 h-4 shrink-0",
os === OperatingSystem.WINDOWS && "p-[2.5px]",
os === OperatingSystem.APPLE && "p-[2.7px]",
os === OperatingSystem.FREEBSD && "p-[1.5px]",
!isSupported && "opacity-50",
)}
>
<OSLogo os={option.os} />
</div>
<div className={cn(!isSupported && "opacity-50")}>
<TextWithTooltip
text={option.name}
maxChars={22}
hideTooltip={!isSupported}
/>
</div>
{!isSupported && (
<div className={"relative"}>
<span className="animate-ping absolute left-0 inline-flex h-[14px] w-[14px] rounded-full bg-netbird opacity-20"></span>
<ArrowUpCircleIcon
size={14}
className={"text-netbird"}
/>
</div>
)}
<LinuxIcon />
<TextWithTooltip text={option.name} maxChars={20} />
</div>
<div
@@ -246,13 +207,12 @@ export function PeerSelector({
value && value.id == option.id
? "text-white"
: "text-nb-gray-300",
!isSupported && "opacity-50",
)}
>
<MapPinIcon />
{option.ip}
</div>
</FullTooltip>
</>
);
}}
/>
-71
View File
@@ -1,71 +0,0 @@
import * as RadioPrimitive from "@radix-ui/react-radio-group";
import { cn } from "@utils/helpers";
import { cva, VariantProps } from "class-variance-authority";
import * as React from "react";
import { forwardRef } from "react";
type RadioVariants = VariantProps<typeof variants>;
const variants = cva([], {
variants: {
variant: {
default: [
"dark:data-[state=unchecked]:bg-nb-gray-950 dark:border-nb-gray-900 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300",
"dark:data-[state=checked]:bg-netbird",
],
},
},
});
const Radio = forwardRef<
React.ElementRef<typeof RadioPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioPrimitive.Root> & RadioVariants
>(
(
{ className, children, variant = "default", defaultValue, ...props },
ref,
) => (
<RadioPrimitive.Root
ref={ref}
defaultValue={defaultValue}
name={props.name}
{...props}
>
{children}
</RadioPrimitive.Root>
),
);
Radio.displayName = RadioPrimitive.Root.displayName;
type Props = {
value: string;
className?: string;
} & RadioVariants;
const RadioItem = ({ value, className, variant = "default" }: Props) => {
return (
<RadioPrimitive.Item
value={value}
className={cn(
variants({ variant }),
"border-neutral-900",
"peer h-5 w-5 shrink-0 rounded-full border",
"ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2",
"disabled:cursor-not-allowed disabled:opacity-50 relative",
className,
)}
>
<RadioPrimitive.Indicator asChild={true}>
<div
className={cn(
"h-2 w-2 bg-netbird absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center rounded-full",
"data-[state=checked]:bg-white data-[state=checked]:text-neutral-50 ",
)}
></div>
</RadioPrimitive.Indicator>
</RadioPrimitive.Item>
);
};
RadioItem.displayName = RadioPrimitive.Item.displayName;
export { Radio, RadioItem };
+5 -1
View File
@@ -1,3 +1,7 @@
export default function Separator() {
return <span className={"h-[1px] w-full bg-zinc-700/40 block"}></span>;
return (
<span
className={"h-[1px] w-full dark:bg-nb-gray-900 bg-nb-gray-100 block"}
></span>
);
}
+21 -28
View File
@@ -36,36 +36,29 @@ const switchVariants = cva("", {
const ToggleSwitch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> &
SwitchVariants & { dataCy?: string }
>(
(
{ className, size = "default", variant = "default", dataCy, ...props },
ref,
) => (
<SwitchPrimitives.Root
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> & SwitchVariants
>(({ className, size = "default", variant = "default", ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 dark:focus-visible:ring-neutral-300 dark:focus-visible:ring-offset-neutral-950 ",
className,
switchVariants({ size, variant }),
)}
{...props}
onClick={(e) => {
e.stopPropagation();
props.onClick?.(e);
}}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 dark:focus-visible:ring-neutral-300 dark:focus-visible:ring-offset-neutral-950 ",
className,
switchVariants({ size, variant }),
switchVariants({ "thumb-size": size }),
"pointer-events-none block rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=unchecked]:translate-x-0 dark:bg-white",
)}
{...props}
data-cy={dataCy}
onClick={(e) => {
e.stopPropagation();
props.onClick?.(e);
}}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
switchVariants({ "thumb-size": size }),
"pointer-events-none block rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=unchecked]:translate-x-0 dark:bg-white",
)}
/>
</SwitchPrimitives.Root>
),
);
/>
</SwitchPrimitives.Root>
));
ToggleSwitch.displayName = SwitchPrimitives.Root.displayName;
export { ToggleSwitch };
+4 -4
View File
@@ -10,9 +10,6 @@ const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
export const tooltipClasses =
"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";
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
@@ -22,7 +19,10 @@ const TooltipContent = React.forwardRef<
ref={ref}
asChild={true}
sideOffset={sideOffset}
className={cn(tooltipClasses, className)}
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,
)}
{...props}
>
<div>{props.children}</div>
+1 -1
View File
@@ -51,7 +51,7 @@ function List({ children }: { children: React.ReactNode }) {
<Tabs.List
className={cn(
"px-4 py-4 whitespace-nowrap overflow-y-hidden shrink-0 no-scrollbar",
"lg:h-full items-start bg-nb-gray border-b-0 border-nb-gray-930",
"lg:h-full items-start bg-nb-gray border-b border-nb-gray-930",
"flex lg:flex-col lg:gap-1",
)}
style={{
Loaded 20 of 86 files, more files were not shown because too many files have changed in this diff. Show more