import { Trans } from "@lingui/react/macro" import { t } from "@lingui/core/macro" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { $publicKey, pb } from "@/lib/stores" import { cn, generateToken, isReadOnlyUser, tokenMap, useLocalStorage } from "@/lib/utils" import { useStore } from "@nanostores/react" import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react" import { memo, useEffect, useMemo, useRef, useState } from "react" import { $router, basePath, Link, navigate } from "./router" import { SystemRecord } from "@/types" import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons" import { InputCopy } from "./ui/input-copy" import { getPagePath } from "@nanostores/router" import { copyDockerCompose, copyDockerRun, copyLinuxCommand, copyWindowsCommand, DropdownItem, InstallDropdown, } from "./install-dropdowns" import { DropdownMenu, DropdownMenuTrigger } from "./ui/dropdown-menu" export function AddSystemButton({ className }: { className?: string }) { const [open, setOpen] = useState(false) let opened = useRef(false) if (open) { opened.current = true } return ( {opened.current && } ) } /** * Token to be used for the next system. * Prevents token changing if user copies config, then closes dialog and opens again. */ let nextSystemToken: string | null = null /** * SystemDialog component for adding or editing a system. * @param {Object} props - The component props. * @param {function} props.setOpen - Function to set the open state of the dialog. * @param {SystemRecord} [props.system] - Optional system record for editing an existing system. */ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) => void; system?: SystemRecord }) => { const publicKey = useStore($publicKey) const port = useRef(null) const [hostValue, setHostValue] = useState(system?.host ?? "") const isUnixSocket = hostValue.startsWith("/") const [tab, setTab] = useLocalStorage("as-tab", "docker") const [token, setToken] = useState(system?.token ?? "") useEffect(() => { ;(async () => { // if no system, generate a new token if (!system) { nextSystemToken ||= generateToken() return setToken(nextSystemToken) } // if system exists,get the token from the fingerprint record if (tokenMap.has(system.id)) { return setToken(tokenMap.get(system.id)!) } const { token } = await pb.collection("fingerprints").getFirstListItem(`system = "${system.id}"`, { fields: "token", }) tokenMap.set(system.id, token) setToken(token) })() }, [system?.id]) async function handleSubmit(e: SubmitEvent) { e.preventDefault() const formData = new FormData(e.target as HTMLFormElement) const data = Object.fromEntries(formData) as Record data.users = pb.authStore.record!.id try { setOpen(false) if (system) { await pb.collection("systems").update(system.id, { ...data, status: "pending" }) } else { const createdSystem = await pb.collection("systems").create(data) await pb.collection("fingerprints").create({ system: createdSystem.id, token, }) // Reset the current token after successful system // creation so next system gets a new token nextSystemToken = null } navigate(basePath) } catch (e) { console.error(e) } } return useMemo( () => ( { setHostValue(system?.host ?? "") }} > {system ? `${t`Edit`} ${system?.name}` : Add New System} Docker Binary {/* Docker (set tab index to prevent auto focusing content in edit system dialog) */} Copy the docker-compose.yml content for the agent below, or register agents automatically with a{" "} setOpen(false)} href={getPagePath($router, "settings", { name: "tokens" })} className="link" > universal token . {/* Binary */} Copy the installation command for the agent below, or register agents automatically with a{" "} setOpen(false)} href={getPagePath($router, "settings", { name: "tokens" })} className="link" > universal token .
{ setHostValue(e.target.value) }} />
{/* Docker */} copyDockerCompose(isUnixSocket ? hostValue : port.current?.value, publicKey, token) } icon={} dropdownItems={[ { text: t({ message: "Copy docker run", context: "Button to copy docker run command" }), onClick: async () => copyDockerRun(isUnixSocket ? hostValue : port.current?.value, publicKey, token), icons: [DockerIcon], }, ]} /> {/* Binary */} } onClick={async () => copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token) } dropdownItems={[ { text: t({ message: "Homebrew command", context: "Button to copy install command" }), onClick: async () => copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token, true), icons: [AppleIcon, TuxIcon], }, { text: t({ message: "Windows command", context: "Button to copy install command" }), onClick: async () => copyWindowsCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token), icons: [WindowsIcon], }, { text: t`Manual setup instructions`, url: "https://beszel.dev/guide/agent-installation#binary", icons: [ExternalLinkIcon], }, ]} /> {/* Save */}
), [] ) } interface CopyButtonProps { text: string onClick: () => void dropdownItems: DropdownItem[] icon?: React.ReactElement } const CopyButton = memo((props: CopyButtonProps) => { return (
) })