diff --git a/apps/dokploy/components/dashboard/project/environment-management.tsx b/apps/dokploy/components/dashboard/project/environment-management.tsx new file mode 100644 index 000000000..929d1f3e1 --- /dev/null +++ b/apps/dokploy/components/dashboard/project/environment-management.tsx @@ -0,0 +1,387 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { Copy, Plus, Settings, Trash2 } from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { DateTooltip } from "@/components/shared/date-tooltip"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { api } from "@/utils/api"; + +const createEnvironmentSchema = z.object({ + name: z.string().min(1, "Environment name is required"), + description: z.string().optional(), +}); + +const duplicateEnvironmentSchema = z.object({ + name: z.string().min(1, "Environment name is required"), + description: z.string().optional(), +}); + +type CreateEnvironment = z.infer; +type DuplicateEnvironment = z.infer; + +interface Props { + projectId: string; + children?: React.ReactNode; +} + +export const EnvironmentManagement = ({ projectId, children }: Props) => { + const [isOpen, setIsOpen] = useState(false); + const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); + const [isDuplicateDialogOpen, setIsDuplicateDialogOpen] = useState(false); + const [selectedEnvironmentId, setSelectedEnvironmentId] = + useState(""); + + const utils = api.useUtils(); + + // Queries + const { data: environments, isLoading: environmentsLoading } = + api.environment.byProjectId.useQuery( + { projectId }, + { enabled: !!projectId }, + ); + + // Mutations + const createEnvironmentMutation = api.environment.create.useMutation(); + const duplicateEnvironmentMutation = api.environment.duplicate.useMutation(); + const deleteEnvironmentMutation = api.environment.remove.useMutation(); + + // Forms + const createForm = useForm({ + defaultValues: { + name: "", + description: "", + }, + resolver: zodResolver(createEnvironmentSchema), + }); + + const duplicateForm = useForm({ + defaultValues: { + name: "", + description: "", + }, + resolver: zodResolver(duplicateEnvironmentSchema), + }); + + const onCreateSubmit = async (formData: CreateEnvironment) => { + try { + await createEnvironmentMutation.mutateAsync({ + ...formData, + projectId, + }); + toast.success("Environment created successfully"); + utils.environment.byProjectId.invalidate({ projectId }); + setIsCreateDialogOpen(false); + createForm.reset(); + } catch (error) { + toast.error("Error creating environment"); + } + }; + + const onDuplicateSubmit = async (formData: DuplicateEnvironment) => { + if (!selectedEnvironmentId) return; + + try { + await duplicateEnvironmentMutation.mutateAsync({ + environmentId: selectedEnvironmentId, + name: formData.name, + description: formData.description, + }); + toast.success("Environment duplicated successfully"); + utils.environment.byProjectId.invalidate({ projectId }); + setIsDuplicateDialogOpen(false); + duplicateForm.reset(); + setSelectedEnvironmentId(""); + } catch (error) { + toast.error("Error duplicating environment"); + } + }; + + const handleDeleteEnvironment = async (environmentId: string) => { + try { + await deleteEnvironmentMutation.mutateAsync({ environmentId }); + toast.success("Environment deleted successfully"); + utils.environment.byProjectId.invalidate({ projectId }); + } catch (error) { + toast.error("Error deleting environment"); + } + }; + + const handleDuplicateClick = ( + environmentId: string, + environmentName: string, + ) => { + setSelectedEnvironmentId(environmentId); + duplicateForm.setValue("name", `${environmentName} (copy)`); + setIsDuplicateDialogOpen(true); + }; + + return ( + <> + + + {children ?? ( + + )} + + + + Environment Management + + Manage project environments. Each environment can have its own + configuration and settings. + + + +
+
+

Project Environments

+ +
+ + {environmentsLoading ? ( +
+
+ Loading environments... +
+
+ ) : environments && environments.length > 0 ? ( +
+ {environments.map((env) => ( +
+
+
+

{env.name}

+ {env.name === "production" && ( + Default + )} +
+ {env.description && ( +

+ {env.description} +

+ )} + + + Created + + +
+
+ + {env.name !== "production" && ( + + handleDeleteEnvironment(env.environmentId) + } + > + + + )} +
+
+ ))} +
+ ) : ( +
+
+ No environments found. The production environment should be + created automatically. +
+
+ )} +
+
+
+ + {/* Create Environment Dialog */} + + + + Create New Environment + + Create a new environment for this project. + + + +
+ + ( + + Environment Name + + + + + + )} + /> + + ( + + Description (Optional) + +