"use client"; import CopyToClipboard from "@app/components/CopyToClipboard"; import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; import { SettingsContainer, SettingsSection, SettingsSectionBody, SettingsSectionDescription, SettingsSectionHeader, SettingsSectionTitle } from "@app/components/Settings"; import HeaderTitle from "@app/components/SettingsSectionTitle"; import { Button } from "@app/components/ui/button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CreateClientBody, CreateClientResponse, PickClientDefaultsResponse } from "@server/routers/client"; import { AxiosResponse } from "axios"; import { ChevronDown, ChevronUp } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { OlmInstallCommands } from "@app/components/olm-install-commands"; import { useTranslations } from "next-intl"; type ClientType = "olm"; interface TunnelTypeOption { id: ClientType; title: string; description: string; disabled?: boolean; } export default function Page() { const { env } = useEnvContext(); const api = createApiClient({ env }); const { orgId } = useParams(); const router = useRouter(); const t = useTranslations(); const createClientFormSchema = z.object({ name: z .string() .min(2, { message: t("nameMin", { len: 2 }) }) .max(30, { message: t("nameMax", { len: 30 }) }), method: z.enum(["olm"]), subnet: z.union([z.ipv4(), z.ipv6()]).refine((val) => val.length > 0, { message: t("subnetRequired") }) }); type CreateClientFormValues = z.infer; const [tunnelTypes, setTunnelTypes] = useState< ReadonlyArray >([ { id: "olm", title: t("olmTunnel"), description: t("olmTunnelDescription"), disabled: true } ]); const [loadingPage, setLoadingPage] = useState(true); const [olmId, setOlmId] = useState(""); const [olmSecret, setOlmSecret] = useState(""); const [olmVersion, setOlmVersion] = useState("latest"); const [createLoading, setCreateLoading] = useState(false); const [showAdvancedSettings, setShowAdvancedSettings] = useState(false); const [clientDefaults, setClientDefaults] = useState(null); const form = useForm({ resolver: zodResolver(createClientFormSchema), defaultValues: { name: "", method: "olm", subnet: "" } }); async function onSubmit(data: CreateClientFormValues) { setCreateLoading(true); if (!clientDefaults) { toast({ variant: "destructive", title: t("errorCreatingClient"), description: t("clientDefaultsNotFound") }); setCreateLoading(false); return; } const payload: CreateClientBody = { name: data.name, type: data.method as "olm", olmId: clientDefaults.olmId, secret: clientDefaults.olmSecret, subnet: data.subnet }; const res = await api .put< AxiosResponse >(`/org/${orgId}/client`, payload) .catch((e) => { toast({ variant: "destructive", title: t("errorCreatingClient"), description: formatAxiosError(e) }); }); if (res && res.status === 201) { const data = res.data.data; router.push(`/${orgId}/settings/clients/machine/${data.niceId}`); } setCreateLoading(false); } useEffect(() => { const load = async () => { setLoadingPage(true); try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 3000); const response = await fetch( `https://api.github.com/repos/fosrl/olm/releases/latest`, { signal: controller.signal } ); clearTimeout(timeoutId); if (!response.ok) { throw new Error( t("olmErrorFetchReleases", { err: response.statusText }) ); } const data = await response.json(); const latestVersion = data.tag_name; setOlmVersion(latestVersion); } catch (error) { if (error instanceof Error && error.name === "AbortError") { console.error(t("olmErrorFetchTimeout")); } else { console.error( t("olmErrorFetchLatest", { err: error instanceof Error ? error.message : String(error) }) ); } } await api .get(`/org/${orgId}/pick-client-defaults`) .catch((e) => { form.setValue("method", "olm"); }) .then((res) => { if (res && res.status === 200) { const data = res.data.data; setClientDefaults(data); const olmId = data.olmId; const olmSecret = data.olmSecret; setOlmId(olmId); setOlmSecret(olmSecret); if (data.subnet) { form.setValue("subnet", data.subnet); } setTunnelTypes((prev: any) => { return prev.map((item: any) => { return { ...item, disabled: false }; }); }); } }); setLoadingPage(false); }; load(); }, []); return ( <>
{!loadingPage && (
{t("clientInformation")}
{ if (e.key === "Enter") { e.preventDefault(); // block default enter refresh } }} className="space-y-4 grid gap-4 grid-cols-1 md:grid-cols-2 items-start" id="create-client-form" > ( {t("name")} {t( "clientNameDescription" )} )} />
{showAdvancedSettings && ( ( {t("clientAddress")} {t( "addressDescription" )} )} /> )}
{form.watch("method") === "olm" && ( <> {t("clientOlmCredentials")} {t( "clientOlmCredentialsDescription" )} {t("olmEndpoint")} {t("olmId")} {t("olmSecretKey")} )}
)} ); }