import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema"; import { Code2, FileInput, Globe2, HardDrive, HelpCircle } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Textarea } from "@/components/ui/textarea"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { slugify } from "@/lib/slug"; import { api } from "@/utils/api"; import { APP_NAME_MESSAGE, APP_NAME_REGEX } from "@/utils/schema"; const AddImportSchema = z.object({ name: z.string().min(1, { message: "Name is required" }), appName: z .string() .min(1, { message: "App name is required" }) .regex(APP_NAME_REGEX, { message: APP_NAME_MESSAGE }), base64: z.string().min(1, { message: "Base64 content is required" }), serverId: z.string().optional(), }); type AddImport = z.infer; type TemplateInfo = { compose: string; template: { domains: Array<{ serviceName: string; port: number; path?: string; host?: string; }>; envs: string[]; mounts: Array<{ filePath: string; content: string }>; }; }; interface Props { environmentId: string; projectName?: string; } export const AddImport = ({ environmentId, projectName }: Props) => { const utils = api.useUtils(); const [visible, setVisible] = useState(false); const [previewOpen, setPreviewOpen] = useState(false); const [mountOpen, setMountOpen] = useState(false); const [selectedMount, setSelectedMount] = useState<{ filePath: string; content: string; } | null>(null); const [templateInfo, setTemplateInfo] = useState(null); const slug = slugify(projectName); const { data: isCloud } = api.settings.isCloud.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery(); const shouldShowServerDropdown = !!(servers && servers.length > 0); const { mutateAsync: previewTemplate, isPending: isProcessing } = api.compose.previewTemplate.useMutation(); const { mutateAsync: createCompose, isPending: isCreating } = api.compose.create.useMutation(); const { mutateAsync: importCompose, isPending: isImporting } = api.compose.import.useMutation(); const form = useForm({ defaultValues: { name: "", appName: `${slug}-`, base64: "" }, resolver: zodResolver(AddImportSchema), }); const resetAll = () => { form.reset({ name: "", appName: `${slug}-`, base64: "" }); setTemplateInfo(null); setPreviewOpen(false); setMountOpen(false); setSelectedMount(null); }; const handleOpenChange = (open: boolean) => { if (!open) resetAll(); setVisible(open); }; const handleLoad = async (data: AddImport) => { try { const result = await previewTemplate({ appName: data.appName, base64: data.base64.trim(), serverId: data.serverId === "dokploy" ? undefined : data.serverId, }); setTemplateInfo(result); setPreviewOpen(true); } catch (error) { toast.error( error instanceof Error ? error.message : "Error processing template", ); } }; const handleImport = async () => { const data = form.getValues(); try { const compose = await createCompose({ name: data.name, appName: data.appName, environmentId, composeType: "docker-compose", serverId: data.serverId === "dokploy" ? undefined : data.serverId, }); await importCompose({ composeId: compose.composeId, base64: data.base64.trim(), }); toast.success("Compose imported successfully"); await utils.environment.one.invalidate({ environmentId }); resetAll(); setVisible(false); } catch (error) { toast.error( error instanceof Error ? error.message : "Error importing compose", ); } }; const handleCancelPreview = () => { setPreviewOpen(false); setTemplateInfo(null); }; return ( <> e.preventDefault()} > Import Import Compose Paste a base64-encoded compose export to preview and import it
( Name { const val = e.target.value || ""; form.setValue( "appName", `${slug}-${slugify(val.trim())}`, ); field.onChange(val); }} /> )} /> {shouldShowServerDropdown && ( ( Select a Server {!isCloud ? "(Optional)" : ""} If no server is selected, the compose will be deployed on the server where the user is logged in. )} /> )} ( App Name )} /> ( Configuration (Base64)