From 6fc325fe95a7f29979e740e481bc830675e2cd88 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 1 Sep 2025 17:36:27 -0600 Subject: [PATCH 01/36] feat(environment): implement environment management with create, duplicate, and delete functionalities; add environment schema and database migrations --- .../project/environment-management.tsx | 387 + .../dokploy/drizzle/0107_charming_chimera.sql | 26 + .../drizzle/0108_keen_doctor_faustus.sql | 111 + apps/dokploy/drizzle/meta/0107_snapshot.json | 6481 ++++++++++++++++ apps/dokploy/drizzle/meta/0108_snapshot.json | 6595 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 14 + .../pages/dashboard/project/[projectId].tsx | 5 +- apps/dokploy/server/api/root.ts | 2 + .../dokploy/server/api/routers/environment.ts | 113 + apps/dokploy/server/api/routers/project.ts | 144 +- packages/server/src/db/schema/application.ts | 10 + packages/server/src/db/schema/compose.ts | 11 + packages/server/src/db/schema/environment.ts | 84 + packages/server/src/db/schema/index.ts | 1 + packages/server/src/db/schema/mariadb.ts | 10 + packages/server/src/db/schema/mongo.ts | 10 + packages/server/src/db/schema/mysql.ts | 9 + packages/server/src/db/schema/postgres.ts | 10 + packages/server/src/db/schema/project.ts | 16 +- packages/server/src/db/schema/redis.ts | 10 + packages/server/src/index.ts | 1 + packages/server/src/services/environment.ts | 113 + packages/server/src/services/project.ts | 27 +- 23 files changed, 14108 insertions(+), 82 deletions(-) create mode 100644 apps/dokploy/components/dashboard/project/environment-management.tsx create mode 100644 apps/dokploy/drizzle/0107_charming_chimera.sql create mode 100644 apps/dokploy/drizzle/0108_keen_doctor_faustus.sql create mode 100644 apps/dokploy/drizzle/meta/0107_snapshot.json create mode 100644 apps/dokploy/drizzle/meta/0108_snapshot.json create mode 100644 apps/dokploy/server/api/routers/environment.ts create mode 100644 packages/server/src/db/schema/environment.ts create mode 100644 packages/server/src/services/environment.ts 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) + +