mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91fc33bfe | ||
|
|
9420214059 | ||
|
|
b949f60afe | ||
|
|
d498e4cc25 | ||
|
|
130dc0c32c |
No files matched your search
+1
-1
@@ -7,6 +7,6 @@
|
||||
"variant": "important",
|
||||
"isExternal": true,
|
||||
"closeable": true,
|
||||
"isCloudOnly": false
|
||||
"targets": ["selfhosted"]
|
||||
}
|
||||
]
|
||||
Generated
+10
-10
@@ -3255,13 +3255,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
@@ -3665,9 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
@@ -6973,9 +6973,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as React from "react";
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
|
||||
export const SkeletonSettings = () => {
|
||||
return (
|
||||
<div className={"p-default py-6 max-w-2xl"}>
|
||||
<Skeleton height={24} width={200} className={"mb-6"} />
|
||||
<Skeleton height={32} width={110} className={"mb-10"} />
|
||||
<div className={"mb-8"}>
|
||||
<Skeleton height={17} width={200} className={"mb-2"} />
|
||||
<Skeleton height={80} width={"100%"} />
|
||||
</div>
|
||||
<div className={"mb-8"}>
|
||||
<Skeleton height={17} width={200} className={"mb-2"} />
|
||||
<Skeleton height={80} width={"100%"} />
|
||||
</div>
|
||||
<Skeleton height={80} width={"100%"} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -8,13 +8,14 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||
import { isLocalDev, isNetBirdHosted } from "@utils/netbird";
|
||||
import { isLocalDev } from "@utils/netbird";
|
||||
import announcementFile from "../../announcements.json";
|
||||
|
||||
const ANNOUNCEMENTS_URL =
|
||||
"https://raw.githubusercontent.com/netbirdio/dashboard/main/announcements.json";
|
||||
const STORAGE_KEY = "netbird-announcements";
|
||||
const CACHE_DURATION_MS = 30 * 60 * 1000;
|
||||
const TARGET = "selfhosted";
|
||||
const BANNER_HEIGHT = 40;
|
||||
|
||||
interface AnnouncementStore {
|
||||
@@ -30,7 +31,7 @@ export interface Announcement extends AnnouncementVariant {
|
||||
linkText?: string;
|
||||
isExternal?: boolean;
|
||||
closeable: boolean;
|
||||
isCloudOnly: boolean;
|
||||
targets?: string[];
|
||||
}
|
||||
|
||||
interface AnnouncementInfo extends Announcement {
|
||||
@@ -76,8 +77,7 @@ const getAnnouncements = async (): Promise<AnnouncementInfo[]> => {
|
||||
raw = await response.json();
|
||||
}
|
||||
|
||||
const isCloud = isNetBirdHosted();
|
||||
const filtered = raw.filter((a) => !a.isCloudOnly || isCloud);
|
||||
const filtered = raw.filter((a) => a.targets?.includes(TARGET));
|
||||
const hashes = new Set(filtered.map((a) => md5(a.text).toString()));
|
||||
const closed = (stored?.closedAnnouncements ?? []).filter((h) =>
|
||||
hashes.has(h),
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface Account {
|
||||
user_approval_required: boolean;
|
||||
};
|
||||
peer_login_expiration_enabled: boolean;
|
||||
peer_expose_enabled?: boolean;
|
||||
peer_expose_groups?: string[];
|
||||
peer_login_expiration: number;
|
||||
peer_inactivity_expiration_enabled: boolean;
|
||||
peer_inactivity_expiration: number;
|
||||
|
||||
@@ -664,6 +664,35 @@ export default function ActivityDescription({ event }: Props) {
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Reverse Proxy
|
||||
*/
|
||||
|
||||
if (event.activity_code == "service.peer.expose")
|
||||
return (
|
||||
<div className={"inline"}>
|
||||
Peer <Value>{m.peer_name}</Value> exposed service{" "}
|
||||
<Value>{m.domain}</Value> with auth{" "}
|
||||
<Value>{m.auth ? "Enabled" : "Disabled"}</Value>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (event.activity_code == "service.peer.unexpose")
|
||||
return (
|
||||
<div className={"inline"}>
|
||||
Peer <Value>{m.peer_name}</Value> unexposed service{" "}
|
||||
<Value>{m.domain}</Value>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (event.activity_code == "service.peer.expose.expire")
|
||||
return (
|
||||
<div className={"inline"}>
|
||||
Service <Value>{m.domain}</Value> exposed by peer{" "}
|
||||
<Value>{m.peer_name}</Value> was removed due to renewal expiration
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Networks
|
||||
*/
|
||||
|
||||
@@ -66,6 +66,7 @@ export default function DNSRecordsTable({ zone }: Props) {
|
||||
className={"bg-nb-gray-960 py-2"}
|
||||
inset={true}
|
||||
text={"DNS Records"}
|
||||
initialPageSize={zone?.records?.length}
|
||||
manualPagination={true}
|
||||
sorting={sorting}
|
||||
columnVisibility={{}}
|
||||
|
||||
@@ -240,7 +240,7 @@ export const GroupsTableColumns: ColumnDef<GroupUsage>[] = [
|
||||
row.setup_keys_count > 0 ||
|
||||
row.users_count > 0 ||
|
||||
row.resources_count > 0 ||
|
||||
row.zones_count
|
||||
row.zones_count > 0
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import InlineLink from "@components/InlineLink";
|
||||
import { Input } from "@components/Input";
|
||||
import { Label } from "@components/Label";
|
||||
import { notify } from "@components/Notification";
|
||||
import { PeerGroupSelector } from "@components/PeerGroupSelector";
|
||||
import {
|
||||
SelectDropdown,
|
||||
SelectOption,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
import { useHasChanges } from "@hooks/useHasChanges";
|
||||
import * as Tabs from "@radix-ui/react-tabs";
|
||||
import { useApiCall } from "@utils/api";
|
||||
import { validator } from "@utils/helpers";
|
||||
import { cn, validator } from "@utils/helpers";
|
||||
import {
|
||||
ClockFadingIcon,
|
||||
ExternalLinkIcon,
|
||||
@@ -27,6 +28,10 @@ import SettingsIcon from "@/assets/icons/SettingsIcon";
|
||||
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||
import { Account } from "@/interfaces/Account";
|
||||
import { SmallBadge } from "@components/ui/SmallBadge";
|
||||
import ReverseProxyIcon from "@/assets/icons/ReverseProxyIcon";
|
||||
import useGroupHelper from "@/modules/groups/useGroupHelper";
|
||||
import { useGroups } from "@/contexts/GroupsProvider";
|
||||
import { SkeletonSettings } from "@components/skeletons/SkeletonSettings";
|
||||
|
||||
type Props = {
|
||||
account: Account;
|
||||
@@ -48,6 +53,16 @@ const latestOrCustomVersion = [
|
||||
] as SelectOption[];
|
||||
|
||||
export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
||||
const { isLoading: isGroupsLoading } = useGroups();
|
||||
|
||||
return isGroupsLoading ? (
|
||||
<SkeletonSettings />
|
||||
) : (
|
||||
<ClientSettingsTabContent account={account} />
|
||||
);
|
||||
}
|
||||
|
||||
function ClientSettingsTabContent({ account }: Readonly<Props>) {
|
||||
const { permission } = usePermissions();
|
||||
|
||||
const { mutate } = useSWRConfig();
|
||||
@@ -69,9 +84,23 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
||||
isCustomVersion ? autoUpdateSetting : "",
|
||||
);
|
||||
|
||||
const [peerExposeEnabled, setPeerExposeEnabled] = useState<boolean>(
|
||||
account?.settings?.peer_expose_enabled ?? false,
|
||||
);
|
||||
const [peerExposeGroups, setPeerExposeGroups, { save: saveGroups }] =
|
||||
useGroupHelper({
|
||||
initial: account.settings?.peer_expose_groups,
|
||||
});
|
||||
const peerExposeGroupNames = useMemo(
|
||||
() => peerExposeGroups.map((g) => g.name).sort(),
|
||||
[peerExposeGroups],
|
||||
);
|
||||
|
||||
const { hasChanges, updateRef } = useHasChanges([
|
||||
autoUpdateMethod,
|
||||
autoUpdateCustomVersion,
|
||||
peerExposeEnabled,
|
||||
peerExposeGroupNames,
|
||||
]);
|
||||
|
||||
const handleUpdateMethodChange = (value: string) => {
|
||||
@@ -99,16 +128,24 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
||||
return (
|
||||
!hasChanges ||
|
||||
!permission.settings.update ||
|
||||
(autoUpdateMethod === "custom" && !canSaveCustomVersion)
|
||||
(autoUpdateMethod === "custom" && !canSaveCustomVersion) ||
|
||||
(peerExposeEnabled && peerExposeGroups.length === 0)
|
||||
);
|
||||
}, [
|
||||
hasChanges,
|
||||
permission.settings.update,
|
||||
autoUpdateMethod,
|
||||
canSaveCustomVersion,
|
||||
peerExposeEnabled,
|
||||
peerExposeGroups,
|
||||
]);
|
||||
|
||||
const saveChanges = async () => {
|
||||
const groups = await saveGroups();
|
||||
const peerExposeGroupIds = groups
|
||||
.map((group) => group.id)
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
notify({
|
||||
title: "Client Settings",
|
||||
description: `Client settings successfully updated.`,
|
||||
@@ -118,11 +155,18 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
||||
settings: {
|
||||
...account.settings,
|
||||
auto_update_version: autoUpdateCustomVersion || autoUpdateMethod,
|
||||
peer_expose_enabled: peerExposeEnabled,
|
||||
peer_expose_groups: peerExposeGroupIds,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
mutate("/accounts");
|
||||
updateRef([autoUpdateMethod, autoUpdateCustomVersion]);
|
||||
updateRef([
|
||||
autoUpdateMethod,
|
||||
autoUpdateCustomVersion,
|
||||
peerExposeEnabled,
|
||||
peerExposeGroupNames,
|
||||
]);
|
||||
}),
|
||||
loadingMessage: "Updating client settings...",
|
||||
});
|
||||
@@ -152,7 +196,7 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
||||
|
||||
return (
|
||||
<Tabs.Content value={"clients"}>
|
||||
<div className={"p-default py-6 max-w-xl"}>
|
||||
<div className={"p-default py-6 max-w-2xl"}>
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item
|
||||
href={"/settings"}
|
||||
@@ -178,7 +222,7 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={"flex flex-col gap-6 w-full mt-8"}>
|
||||
<div className={"flex flex-col gap-10 w-full mt-8"}>
|
||||
<div className={"flex flex-col relative"}>
|
||||
<Label>
|
||||
<RefreshCcw size={15} />
|
||||
@@ -223,7 +267,63 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={"mt-3"}>
|
||||
<div>
|
||||
<div>
|
||||
<Label>
|
||||
<ReverseProxyIcon size={15} className={"fill-nb-gray-300"} />
|
||||
Expose Services from CLI
|
||||
</Label>
|
||||
<HelpText>
|
||||
Allow peers to expose local services through the NetBird reverse
|
||||
proxy using the CLI. <br /> This requires at least NetBird{" "}
|
||||
<span className={"text-white font-medium"}>v0.66.0</span>.{" "}
|
||||
<InlineLink
|
||||
href={
|
||||
"https://docs.netbird.io/manage/reverse-proxy/expose-from-cli"
|
||||
}
|
||||
target={"_blank"}
|
||||
>
|
||||
Learn more
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</HelpText>
|
||||
</div>
|
||||
|
||||
<FancyToggleSwitch
|
||||
className={"mt-2"}
|
||||
value={peerExposeEnabled}
|
||||
onChange={setPeerExposeEnabled}
|
||||
label={"Enable Peer Expose"}
|
||||
helpText={
|
||||
"When enabled, peers can expose local HTTP services accessible via a public URL."
|
||||
}
|
||||
disabled={!permission.settings.update}
|
||||
/>
|
||||
|
||||
<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]",
|
||||
!peerExposeEnabled
|
||||
? "opacity-50 pointer-events-none"
|
||||
: "bg-nb-gray-930/80",
|
||||
)}
|
||||
>
|
||||
<div className={"mt-2"}>
|
||||
<Label>Allowed peer groups</Label>
|
||||
<HelpText>
|
||||
Select which peer groups are allowed to expose services. At
|
||||
least one group is required.
|
||||
</HelpText>
|
||||
<PeerGroupSelector
|
||||
values={peerExposeGroups}
|
||||
onChange={setPeerExposeGroups}
|
||||
placeholder="Select peer groups..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>
|
||||
<FlaskConicalIcon size={15} />
|
||||
Experimental
|
||||
@@ -241,25 +341,26 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
</HelpText>
|
||||
<FancyToggleSwitch
|
||||
className={"mt-2"}
|
||||
value={lazyConnection}
|
||||
onChange={toggleLazyConnection}
|
||||
label={
|
||||
<>
|
||||
<ClockFadingIcon size={15} />
|
||||
Enable Lazy Connections
|
||||
</>
|
||||
}
|
||||
helpText={
|
||||
<>
|
||||
Allow to establish connections between peers only when
|
||||
required. This requires NetBird client v0.45 or higher.
|
||||
Changes will only take effect after restarting the clients.
|
||||
</>
|
||||
}
|
||||
disabled={!permission.settings.update}
|
||||
/>
|
||||
</div>
|
||||
<FancyToggleSwitch
|
||||
value={lazyConnection}
|
||||
onChange={toggleLazyConnection}
|
||||
label={
|
||||
<>
|
||||
<ClockFadingIcon size={15} />
|
||||
Enable Lazy Connections
|
||||
</>
|
||||
}
|
||||
helpText={
|
||||
<>
|
||||
Allow to establish connections between peers only when required.
|
||||
This requires NetBird client v0.45 or higher. Changes will only
|
||||
take effect after restarting the clients.
|
||||
</>
|
||||
}
|
||||
disabled={!permission.settings.update}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
Reference in new issue
Block a user