Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b949f60afe | ||
|
|
d498e4cc25 |
20
src/components/skeletons/SkeletonSettings.tsx
Normal file
20
src/components/skeletons/SkeletonSettings.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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={{}}
|
||||
|
||||
@@ -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