From 59cbc8ee0d8b560c7d5a265e199422b0761f13e9 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 1 Sep 2025 21:09:30 -0600 Subject: [PATCH] refactor: update environment selector and API routes to utilize environmentId for service management; enhance UI with Badge component for production environments --- .../project/advanced-environment-selector.tsx | 22 +-- .../environment/[environmentId].tsx | 126 +++++++++++++----- .../dokploy/server/api/routers/application.ts | 10 +- apps/dokploy/server/api/routers/compose.ts | 10 +- apps/dokploy/server/api/routers/mariadb.ts | 10 +- apps/dokploy/server/api/routers/mongo.ts | 10 +- apps/dokploy/server/api/routers/mysql.ts | 10 +- apps/dokploy/server/api/routers/postgres.ts | 10 +- apps/dokploy/server/api/routers/redis.ts | 10 +- 9 files changed, 136 insertions(+), 82 deletions(-) diff --git a/apps/dokploy/components/dashboard/project/advanced-environment-selector.tsx b/apps/dokploy/components/dashboard/project/advanced-environment-selector.tsx index b5cb9aa31..fa7ab47e9 100644 --- a/apps/dokploy/components/dashboard/project/advanced-environment-selector.tsx +++ b/apps/dokploy/components/dashboard/project/advanced-environment-selector.tsx @@ -23,6 +23,7 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "sonner"; import { ChevronDownIcon, PlusIcon, PencilIcon, TrashIcon } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; interface Environment { environmentId: string; @@ -165,9 +166,9 @@ export const AdvancedEnvironmentSelector = ({
{currentEnv?.name || "Select Environment"} {currentEnv?.name === "production" && ( - + Prod - + )}
@@ -189,9 +190,9 @@ export const AdvancedEnvironmentSelector = ({
{environment.name} {environment.name === "production" && ( - + Prod - + )}
{environment.environmentId === currentEnvironmentId && ( @@ -241,7 +242,6 @@ export const AdvancedEnvironmentSelector = ({ - {/* Create Environment Dialog */} @@ -285,9 +285,9 @@ export const AdvancedEnvironmentSelector = ({ @@ -338,9 +338,9 @@ export const AdvancedEnvironmentSelector = ({ @@ -370,9 +370,9 @@ export const AdvancedEnvironmentSelector = ({ diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx index d8ab5953c..7a6e69867 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx @@ -275,12 +275,19 @@ const EnvironmentPage = ( const { data: currentEnvironment } = api.environment.one.useQuery({ environmentId, }); + const { data: allProjects } = api.project.all.useQuery(); const router = useRouter(); - const [isMoveDialogOpen, setIsMoveDialogOpen] = useState(false); const [selectedTargetProject, setSelectedTargetProject] = useState(""); + const [selectedTargetEnvironment, setSelectedTargetEnvironment] = + useState(""); + + const { data: selectedProjectEnvironments } = api.environment.byProjectId.useQuery( + { projectId: selectedTargetProject }, + { enabled: !!selectedTargetProject } + ); const emptyServices = !currentEnvironment || @@ -484,6 +491,10 @@ const EnvironmentPage = ( toast.error("Please select a target project"); return; } + if (!selectedTargetEnvironment) { + toast.error("Please select a target environment"); + return; + } let success = 0; setIsBulkActionLoading(true); @@ -492,50 +503,54 @@ const EnvironmentPage = ( const service = filteredServices.find((s) => s.id === serviceId); if (!service) continue; + // TODO: Update move APIs to use targetEnvironmentId instead of targetProjectId switch (service.type) { case "application": await applicationActions.move.mutateAsync({ applicationId: serviceId, - targetProjectId: selectedTargetProject, + targetEnvironmentId: selectedTargetEnvironment, }); break; case "compose": await composeActions.move.mutateAsync({ composeId: serviceId, - targetProjectId: selectedTargetProject, + targetEnvironmentId: selectedTargetEnvironment, }); break; case "postgres": await postgresActions.move.mutateAsync({ postgresId: serviceId, - targetProjectId: selectedTargetProject, + targetEnvironmentId: selectedTargetEnvironment, }); break; case "mysql": await mysqlActions.move.mutateAsync({ mysqlId: serviceId, - targetProjectId: selectedTargetProject, + targetEnvironmentId: selectedTargetEnvironment, }); break; case "mariadb": await mariadbActions.move.mutateAsync({ mariadbId: serviceId, - targetProjectId: selectedTargetProject, + targetEnvironmentId: selectedTargetEnvironment, }); break; case "redis": await redisActions.move.mutateAsync({ redisId: serviceId, - targetProjectId: selectedTargetProject, + targetEnvironmentId: selectedTargetEnvironment, }); break; case "mongo": await mongoActions.move.mutateAsync({ mongoId: serviceId, - targetProjectId: selectedTargetProject, + targetEnvironmentId: selectedTargetEnvironment, }); break; } + await utils.environment.one.invalidate({ + environmentId, + }); success++; } catch (error) { toast.error( @@ -551,6 +566,9 @@ const EnvironmentPage = ( setIsDropdownOpen(false); setIsMoveDialogOpen(false); setIsBulkActionLoading(false); + // Reset move dialog state + setSelectedTargetProject(""); + setSelectedTargetEnvironment(""); }; const handleBulkDelete = async (deleteVolumes = false) => { @@ -964,14 +982,12 @@ const EnvironmentPage = ( Move Services - Select the target project to move{" "} + Select the target project and environment to move{" "} {selectedServices.length} services
- {projectData?.environments?.filter( - (p) => p.projectId !== projectId, - ).length === 0 ? ( + {allProjects?.filter((p) => p.projectId !== projectId).length === 0 ? (

@@ -980,32 +996,70 @@ const EnvironmentPage = (

) : ( - + <> + {/* Step 1: Select Project */} +
+ + +
+ + {/* Step 2: Select Environment (only show if project is selected) */} + {selectedTargetProject && ( +
+ + +
+ )} + )}
@@ -1013,9 +1067,9 @@ const EnvironmentPage = ( onClick={handleBulkMove} isLoading={isBulkActionLoading} disabled={ - projectData?.environments?.filter( - (p) => p.projectId !== projectId, - ).length === 0 + allProjects?.filter((p) => p.projectId !== projectId).length === 0 || + !selectedTargetProject || + !selectedTargetEnvironment } > Move Services diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 953c8d122..ef85e6e9e 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -819,7 +819,7 @@ export const applicationRouter = createTRPCRouter({ .input( z.object({ applicationId: z.string(), - targetProjectId: z.string(), + targetEnvironmentId: z.string(), }), ) .mutation(async ({ input, ctx }) => { @@ -833,11 +833,11 @@ export const applicationRouter = createTRPCRouter({ }); } - const targetProject = await findProjectById(input.targetProjectId); - if (targetProject.organizationId !== ctx.session.activeOrganizationId) { + const targetEnvironment = await findEnvironmentById(input.targetEnvironmentId); + if (targetEnvironment.project.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to move to this project", + message: "You are not authorized to move to this environment", }); } @@ -845,7 +845,7 @@ export const applicationRouter = createTRPCRouter({ const updatedApplication = await db .update(applications) .set({ - projectId: input.targetProjectId, + environmentId: input.targetEnvironmentId, }) .where(eq(applications.applicationId, input.applicationId)) .returning() diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 81556486e..9a4b050c3 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -655,7 +655,7 @@ export const composeRouter = createTRPCRouter({ .input( z.object({ composeId: z.string(), - targetProjectId: z.string(), + targetEnvironmentId: z.string(), }), ) .mutation(async ({ input, ctx }) => { @@ -667,18 +667,18 @@ export const composeRouter = createTRPCRouter({ }); } - const targetProject = await findProjectById(input.targetProjectId); - if (targetProject.organizationId !== ctx.session.activeOrganizationId) { + const targetEnvironment = await findEnvironmentById(input.targetEnvironmentId); + if (targetEnvironment.project.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to move to this project", + message: "You are not authorized to move to this environment", }); } const updatedCompose = await db .update(composeTable) .set({ - projectId: input.targetProjectId, + environmentId: input.targetEnvironmentId, }) .where(eq(composeTable.composeId, input.composeId)) .returning() diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index 399d57dcd..055689d61 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -337,7 +337,7 @@ export const mariadbRouter = createTRPCRouter({ .input( z.object({ mariadbId: z.string(), - targetProjectId: z.string(), + targetEnvironmentId: z.string(), }), ) .mutation(async ({ input, ctx }) => { @@ -349,11 +349,11 @@ export const mariadbRouter = createTRPCRouter({ }); } - const targetProject = await findProjectById(input.targetProjectId); - if (targetProject.organizationId !== ctx.session.activeOrganizationId) { + const targetEnvironment = await findEnvironmentById(input.targetEnvironmentId); + if (targetEnvironment.project.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to move to this project", + message: "You are not authorized to move to this environment", }); } @@ -361,7 +361,7 @@ export const mariadbRouter = createTRPCRouter({ const updatedMariadb = await db .update(mariadbTable) .set({ - projectId: input.targetProjectId, + environmentId: input.targetEnvironmentId, }) .where(eq(mariadbTable.mariadbId, input.mariadbId)) .returning() diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index 843e1b677..f73a715bf 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -351,7 +351,7 @@ export const mongoRouter = createTRPCRouter({ .input( z.object({ mongoId: z.string(), - targetProjectId: z.string(), + targetEnvironmentId: z.string(), }), ) .mutation(async ({ input, ctx }) => { @@ -363,11 +363,11 @@ export const mongoRouter = createTRPCRouter({ }); } - const targetProject = await findProjectById(input.targetProjectId); - if (targetProject.organizationId !== ctx.session.activeOrganizationId) { + const targetEnvironment = await findEnvironmentById(input.targetEnvironmentId); + if (targetEnvironment.project.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to move to this project", + message: "You are not authorized to move to this environment", }); } @@ -375,7 +375,7 @@ export const mongoRouter = createTRPCRouter({ const updatedMongo = await db .update(mongoTable) .set({ - projectId: input.targetProjectId, + environmentId: input.targetEnvironmentId, }) .where(eq(mongoTable.mongoId, input.mongoId)) .returning() diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index bfe839303..497c7fdb8 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -346,7 +346,7 @@ export const mysqlRouter = createTRPCRouter({ .input( z.object({ mysqlId: z.string(), - targetProjectId: z.string(), + targetEnvironmentId: z.string(), }), ) .mutation(async ({ input, ctx }) => { @@ -358,11 +358,11 @@ export const mysqlRouter = createTRPCRouter({ }); } - const targetProject = await findProjectById(input.targetProjectId); - if (targetProject.organizationId !== ctx.session.activeOrganizationId) { + const targetEnvironment = await findEnvironmentById(input.targetEnvironmentId); + if (targetEnvironment.project.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to move to this project", + message: "You are not authorized to move to this environment", }); } @@ -370,7 +370,7 @@ export const mysqlRouter = createTRPCRouter({ const updatedMysql = await db .update(mysqlTable) .set({ - projectId: input.targetProjectId, + environmentId: input.targetEnvironmentId, }) .where(eq(mysqlTable.mysqlId, input.mysqlId)) .returning() diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index b3dc8bd1a..04a10baaf 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -367,7 +367,7 @@ export const postgresRouter = createTRPCRouter({ .input( z.object({ postgresId: z.string(), - targetProjectId: z.string(), + targetEnvironmentId: z.string(), }), ) .mutation(async ({ input, ctx }) => { @@ -381,11 +381,11 @@ export const postgresRouter = createTRPCRouter({ }); } - const targetProject = await findProjectById(input.targetProjectId); - if (targetProject.organizationId !== ctx.session.activeOrganizationId) { + const targetEnvironment = await findEnvironmentById(input.targetEnvironmentId); + if (targetEnvironment.project.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to move to this project", + message: "You are not authorized to move to this environment", }); } @@ -393,7 +393,7 @@ export const postgresRouter = createTRPCRouter({ const updatedPostgres = await db .update(postgresTable) .set({ - projectId: input.targetProjectId, + environmentId: input.targetEnvironmentId, }) .where(eq(postgresTable.postgresId, input.postgresId)) .returning() diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index 2061fe881..b842e22f6 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -330,7 +330,7 @@ export const redisRouter = createTRPCRouter({ .input( z.object({ redisId: z.string(), - targetProjectId: z.string(), + targetEnvironmentId: z.string(), }), ) .mutation(async ({ input, ctx }) => { @@ -342,11 +342,11 @@ export const redisRouter = createTRPCRouter({ }); } - const targetProject = await findProjectById(input.targetProjectId); - if (targetProject.organizationId !== ctx.session.activeOrganizationId) { + const targetEnvironment = await findEnvironmentById(input.targetEnvironmentId); + if (targetEnvironment.project.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to move to this project", + message: "You are not authorized to move to this environment", }); } @@ -354,7 +354,7 @@ export const redisRouter = createTRPCRouter({ const updatedRedis = await db .update(redisTable) .set({ - projectId: input.targetProjectId, + environmentId: input.targetEnvironmentId, }) .where(eq(redisTable.redisId, input.redisId)) .returning()