|
|
|
|
@@ -1,16 +1,27 @@
|
|
|
|
|
import Breadcrumbs from "@components/Breadcrumbs";
|
|
|
|
|
import Button from "@components/Button";
|
|
|
|
|
import FancyToggleSwitch from "@components/FancyToggleSwitch";
|
|
|
|
|
import HelpText from "@components/HelpText";
|
|
|
|
|
import InlineLink from "@components/InlineLink";
|
|
|
|
|
import { Input } from "@components/Input";
|
|
|
|
|
import { Label } from "@components/Label";
|
|
|
|
|
import { notify } from "@components/Notification";
|
|
|
|
|
import {
|
|
|
|
|
SelectDropdown,
|
|
|
|
|
SelectOption,
|
|
|
|
|
} from "@components/select/SelectDropdown";
|
|
|
|
|
import { useHasChanges } from "@hooks/useHasChanges";
|
|
|
|
|
import * as Tabs from "@radix-ui/react-tabs";
|
|
|
|
|
import { useApiCall } from "@utils/api";
|
|
|
|
|
import { validator } from "@utils/helpers";
|
|
|
|
|
import {
|
|
|
|
|
ClockFadingIcon,
|
|
|
|
|
ExternalLinkIcon,
|
|
|
|
|
FlaskConicalIcon,
|
|
|
|
|
MonitorSmartphoneIcon,
|
|
|
|
|
RefreshCcw,
|
|
|
|
|
} from "lucide-react";
|
|
|
|
|
import React, { useState } from "react";
|
|
|
|
|
import React, { useMemo, useState } from "react";
|
|
|
|
|
import { useSWRConfig } from "swr";
|
|
|
|
|
import SettingsIcon from "@/assets/icons/SettingsIcon";
|
|
|
|
|
import { usePermissions } from "@/contexts/PermissionsProvider";
|
|
|
|
|
@@ -20,6 +31,21 @@ type Props = {
|
|
|
|
|
account: Account;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const latestOrCustomVersion = [
|
|
|
|
|
{
|
|
|
|
|
label: "Disabled",
|
|
|
|
|
value: "disabled",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: "Latest Version",
|
|
|
|
|
value: "latest",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: "Custom Version",
|
|
|
|
|
value: "custom",
|
|
|
|
|
},
|
|
|
|
|
] as SelectOption[];
|
|
|
|
|
|
|
|
|
|
export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
|
|
|
|
const { permission } = usePermissions();
|
|
|
|
|
|
|
|
|
|
@@ -30,6 +56,77 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
|
|
|
|
account.settings?.lazy_connection_enabled ?? false,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const autoUpdateSetting = account.settings?.auto_update_version;
|
|
|
|
|
const isAutoUpdateEnabled =
|
|
|
|
|
!!autoUpdateSetting && autoUpdateSetting !== "disabled";
|
|
|
|
|
const isCustomVersion = validator.isValidVersion(autoUpdateSetting);
|
|
|
|
|
const [autoUpdateMethod, setAutoUpdateMethod] = useState(
|
|
|
|
|
isAutoUpdateEnabled ? (isCustomVersion ? "custom" : "latest") : "disabled",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const [autoUpdateCustomVersion, setAutoUpdateCustomVersion] = useState(
|
|
|
|
|
isCustomVersion ? autoUpdateSetting : "",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const { hasChanges, updateRef } = useHasChanges([
|
|
|
|
|
autoUpdateMethod,
|
|
|
|
|
autoUpdateCustomVersion,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const handleUpdateMethodChange = (value: string) => {
|
|
|
|
|
setAutoUpdateMethod(value);
|
|
|
|
|
if (value === "disabled" || value === "latest") {
|
|
|
|
|
setAutoUpdateCustomVersion("");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const versionError = useMemo(() => {
|
|
|
|
|
const msg = "Please enter a valid version, e.g., 0.2, 0.2.0, 0.2.0-alpha.1";
|
|
|
|
|
if (autoUpdateCustomVersion == "") return "";
|
|
|
|
|
if (autoUpdateCustomVersion == "-") return "";
|
|
|
|
|
const validSemver = validator.isValidVersion(autoUpdateCustomVersion);
|
|
|
|
|
if (!validSemver) return msg;
|
|
|
|
|
return "";
|
|
|
|
|
}, [autoUpdateCustomVersion]);
|
|
|
|
|
|
|
|
|
|
const canSaveCustomVersion =
|
|
|
|
|
autoUpdateCustomVersion !== "" &&
|
|
|
|
|
autoUpdateMethod === "custom" &&
|
|
|
|
|
versionError === "";
|
|
|
|
|
|
|
|
|
|
const isSaveButtonDisabled = useMemo(() => {
|
|
|
|
|
return (
|
|
|
|
|
!hasChanges ||
|
|
|
|
|
!permission.settings.update ||
|
|
|
|
|
(autoUpdateMethod === "custom" && !canSaveCustomVersion)
|
|
|
|
|
);
|
|
|
|
|
}, [
|
|
|
|
|
hasChanges,
|
|
|
|
|
permission.settings.update,
|
|
|
|
|
autoUpdateMethod,
|
|
|
|
|
canSaveCustomVersion,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const saveChanges = async () => {
|
|
|
|
|
notify({
|
|
|
|
|
title: "Client Settings",
|
|
|
|
|
description: `Client settings successfully updated.`,
|
|
|
|
|
promise: saveRequest
|
|
|
|
|
.put({
|
|
|
|
|
id: account.id,
|
|
|
|
|
settings: {
|
|
|
|
|
...account.settings,
|
|
|
|
|
auto_update_version: autoUpdateCustomVersion || autoUpdateMethod,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.then(() => {
|
|
|
|
|
mutate("/accounts");
|
|
|
|
|
updateRef([autoUpdateMethod, autoUpdateCustomVersion]);
|
|
|
|
|
}),
|
|
|
|
|
loadingMessage: "Updating client settings...",
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleLazyConnection = async (toggle: boolean) => {
|
|
|
|
|
notify({
|
|
|
|
|
title: "Lazy Connections",
|
|
|
|
|
@@ -70,10 +167,48 @@ export default function ClientSettingsTab({ account }: Readonly<Props>) {
|
|
|
|
|
</Breadcrumbs>
|
|
|
|
|
<div className={"flex items-start justify-between"}>
|
|
|
|
|
<h1>Clients</h1>
|
|
|
|
|
<Button
|
|
|
|
|
variant={"primary"}
|
|
|
|
|
disabled={isSaveButtonDisabled}
|
|
|
|
|
onClick={saveChanges}
|
|
|
|
|
data-cy={"save-clients-settings"}
|
|
|
|
|
>
|
|
|
|
|
Save Changes
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={"flex flex-col gap-6 w-full mt-8"}>
|
|
|
|
|
<div className={"mt-0"}>
|
|
|
|
|
<div className={"flex flex-col relative"}>
|
|
|
|
|
<Label>
|
|
|
|
|
<RefreshCcw size={15} />
|
|
|
|
|
Automatic Updates
|
|
|
|
|
</Label>
|
|
|
|
|
<HelpText>
|
|
|
|
|
Select how NetBird clients handle automatic updates by choosing
|
|
|
|
|
the latest version, a custom version, or disabling updates
|
|
|
|
|
altogether.
|
|
|
|
|
</HelpText>
|
|
|
|
|
<div className={"gap-4 items-center grid grid-cols-2"}>
|
|
|
|
|
<SelectDropdown
|
|
|
|
|
value={autoUpdateMethod}
|
|
|
|
|
onChange={handleUpdateMethodChange}
|
|
|
|
|
options={latestOrCustomVersion}
|
|
|
|
|
/>
|
|
|
|
|
<Input
|
|
|
|
|
value={autoUpdateCustomVersion}
|
|
|
|
|
customPrefix={"Version"}
|
|
|
|
|
placeholder={"e.g., 0.52.2"}
|
|
|
|
|
error={versionError}
|
|
|
|
|
errorTooltip={true}
|
|
|
|
|
disabled={autoUpdateMethod !== "custom"}
|
|
|
|
|
onChange={(v) => {
|
|
|
|
|
setAutoUpdateCustomVersion(v.target.value);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={"mt-3"}>
|
|
|
|
|
<h2 className={"text-lg font-medium"}>
|
|
|
|
|
Experimental
|
|
|
|
|
<FlaskConicalIcon
|
|
|
|
|
|