From 4ede21eda93d2e33ff71aab0c8f880c808c6a312 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 1 Mar 2026 13:42:34 -0600 Subject: [PATCH 1/8] feat: enhance project and environment services with additional column selections Updated project and environment services to include specific column selections for various database entities. This improves data retrieval efficiency and allows for more granular control over the returned data structure. Added columns for application, mariadb, mongo, mysql, postgres, redis, and compose entities, as well as enhancements to the environment query structure. --- apps/dokploy/server/api/routers/project.ts | 65 ++++++++++- packages/server/src/services/environment.ts | 115 ++++++++++++++++++-- 2 files changed, 167 insertions(+), 13 deletions(-) diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index ee353594c..3c9cc23cc 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -220,30 +220,55 @@ export const projectRouter = createTRPCRouter({ accessedServices, ), with: { domains: true }, + columns: { + applicationId: true, + }, }, mariadb: { where: buildServiceFilter(mariadb.mariadbId, accessedServices), + columns: { + mariadbId: true, + }, }, mongo: { where: buildServiceFilter(mongo.mongoId, accessedServices), + columns: { + mongoId: true, + }, }, mysql: { where: buildServiceFilter(mysql.mysqlId, accessedServices), + columns: { + mysqlId: true, + }, }, postgres: { where: buildServiceFilter( postgres.postgresId, accessedServices, ), + columns: { + postgresId: true, + }, }, redis: { where: buildServiceFilter(redis.redisId, accessedServices), + columns: { + redisId: true, + }, }, compose: { where: buildServiceFilter(compose.composeId, accessedServices), with: { domains: true }, + columns: { + composeId: true, + }, }, }, + columns: { + environmentId: true, + isDefault: true, + }, }, }, orderBy: desc(projects.createdAt), @@ -258,18 +283,48 @@ export const projectRouter = createTRPCRouter({ with: { domains: true, }, + columns: { + applicationId: true, + }, + }, + mariadb: { + columns: { + mariadbId: true, + }, + }, + mongo: { + columns: { + mongoId: true, + }, + }, + mysql: { + columns: { + mysqlId: true, + }, + }, + postgres: { + columns: { + postgresId: true, + }, + }, + redis: { + columns: { + redisId: true, + }, }, - mariadb: true, - mongo: true, - mysql: true, - postgres: true, - redis: true, compose: { with: { domains: true, }, + columns: { + composeId: true, + }, }, }, + columns: { + environmentId: true, + isDefault: true, + }, }, }, where: eq(projects.organizationId, ctx.session.activeOrganizationId), diff --git a/packages/server/src/services/environment.ts b/packages/server/src/services/environment.ts index d37e7b789..bf4920193 100644 --- a/packages/server/src/services/environment.ts +++ b/packages/server/src/services/environment.ts @@ -34,42 +34,134 @@ export const createEnvironment = async ( export const findEnvironmentById = async (environmentId: string) => { const environment = await db.query.environments.findFirst({ where: eq(environments.environmentId, environmentId), + columns: { + name: true, + description: true, + environmentId: true, + isDefault: true, + projectId: true, + env: true, + }, with: { applications: { with: { - deployments: true, - server: true, + server: { + columns: { + name: true, + serverId: true, + }, + }, + }, + columns: { + name: true, + applicationId: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, }, }, mariadb: { with: { - server: true, + server: { + columns: { + name: true, + serverId: true, + }, + }, + }, + columns: { + mariadbId: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, }, }, mongo: { with: { - server: true, + server: { + columns: { + name: true, + serverId: true, + }, + }, + }, + columns: { + mongoId: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, }, }, mysql: { with: { server: true, }, + columns: { + mysqlId: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, + }, }, postgres: { with: { - server: true, + server: { + columns: { + name: true, + serverId: true, + }, + }, + }, + columns: { + postgresId: true, + name: true, + description: true, + createdAt: true, + applicationStatus: true, + serverId: true, }, }, redis: { with: { - server: true, + server: { + columns: { + name: true, + serverId: true, + }, + }, + }, + columns: { + redisId: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, }, }, compose: { with: { - deployments: true, - server: true, + server: { + columns: { + name: true, + serverId: true, + }, + }, + }, + columns: { + composeId: true, + name: true, + createdAt: true, + composeStatus: true, + description: true, + serverId: true, }, }, project: true, @@ -98,6 +190,12 @@ export const findEnvironmentsByProjectId = async (projectId: string) => { compose: true, project: true, }, + columns: { + name: true, + description: true, + environmentId: true, + isDefault: true, + }, }); return projectEnvironments; }; @@ -169,6 +267,7 @@ export const duplicateEnvironment = async ( name: input.name, description: input.description || originalEnvironment.description, projectId: originalEnvironment.projectId, + env: originalEnvironment.env, }) .returning() .then((value) => value[0]); From a8a5e1c6f108ebef4b942aa87e41328348694ca2 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 1 Mar 2026 13:47:38 -0600 Subject: [PATCH 2/8] refactor: remove unused environment property in duplicateEnvironment function Eliminated the 'env' property from the duplicateEnvironment function to streamline the code and improve clarity. This change enhances maintainability by removing unnecessary parameters. --- .../components/dashboard/project/duplicate-project.tsx | 1 - .../project/[projectId]/environment/[environmentId].tsx | 8 -------- 2 files changed, 9 deletions(-) diff --git a/apps/dokploy/components/dashboard/project/duplicate-project.tsx b/apps/dokploy/components/dashboard/project/duplicate-project.tsx index f84cf35dd..e754b1d8b 100644 --- a/apps/dokploy/components/dashboard/project/duplicate-project.tsx +++ b/apps/dokploy/components/dashboard/project/duplicate-project.tsx @@ -25,7 +25,6 @@ import { import { api } from "@/utils/api"; export type Services = { - appName: string; serverId?: string | null; name: string; type: diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx index af901311e..89d77af24 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx @@ -100,7 +100,6 @@ import { appRouter } from "@/server/api/root"; import { api } from "@/utils/api"; export type Services = { - appName: string; serverId?: string | null; serverName?: string | null; name: string; @@ -146,7 +145,6 @@ export const extractServicesFromEnvironment = ( } } return { - appName: item.appName, name: item.name, type: "application", id: item.applicationId, @@ -161,7 +159,6 @@ export const extractServicesFromEnvironment = ( const mariadb: Services[] = environment.mariadb?.map((item) => ({ - appName: item.appName, name: item.name, type: "mariadb", id: item.mariadbId, @@ -174,7 +171,6 @@ export const extractServicesFromEnvironment = ( const postgres: Services[] = environment.postgres?.map((item) => ({ - appName: item.appName, name: item.name, type: "postgres", id: item.postgresId, @@ -187,7 +183,6 @@ export const extractServicesFromEnvironment = ( const mongo: Services[] = environment.mongo?.map((item) => ({ - appName: item.appName, name: item.name, type: "mongo", id: item.mongoId, @@ -200,7 +195,6 @@ export const extractServicesFromEnvironment = ( const redis: Services[] = environment.redis?.map((item) => ({ - appName: item.appName, name: item.name, type: "redis", id: item.redisId, @@ -213,7 +207,6 @@ export const extractServicesFromEnvironment = ( const mysql: Services[] = environment.mysql?.map((item) => ({ - appName: item.appName, name: item.name, type: "mysql", id: item.mysqlId, @@ -242,7 +235,6 @@ export const extractServicesFromEnvironment = ( } } return { - appName: item.appName, name: item.name, type: "compose", id: item.composeId, From 149293f4d33e0c04a2ad8a5d57e462e97349c0b5 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 1 Mar 2026 13:57:17 -0600 Subject: [PATCH 3/8] feat: enhance mysql configuration with specific column selections Updated the mysql configuration in the environment service to include specific column selections for the server object. This change improves data structure clarity and allows for more precise data handling in future queries. --- packages/server/src/services/environment.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/server/src/services/environment.ts b/packages/server/src/services/environment.ts index bf4920193..9be18a287 100644 --- a/packages/server/src/services/environment.ts +++ b/packages/server/src/services/environment.ts @@ -99,7 +99,12 @@ export const findEnvironmentById = async (environmentId: string) => { }, mysql: { with: { - server: true, + server: { + columns: { + name: true, + serverId: true, + }, + }, }, columns: { mysqlId: true, From a360a259f5961a29542af7d1f1afe9765098bec8 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 1 Mar 2026 14:02:00 -0600 Subject: [PATCH 4/8] feat: add admin-only endpoint for project permissions with detailed environment data Introduced a new API endpoint `allForPermissions` to retrieve projects along with their environments and services specifically for admin users. This enhancement allows for a more comprehensive permissions UI by including detailed information about each environment and its associated applications, improving the overall user experience in managing permissions. --- .../settings/users/add-permissions.tsx | 12 +- apps/dokploy/server/api/routers/project.ts | 106 +++++++++++++++++- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx index d3f8af31c..6fff4e80c 100644 --- a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx +++ b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx @@ -28,8 +28,12 @@ import { import { Switch } from "@/components/ui/switch"; import { api, type RouterOutputs } from "@/utils/api"; -type Project = RouterOutputs["project"]["all"][number]; -type Environment = Project["environments"][number]; +/** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */ +type ProjectForPermissions = RouterOutputs["project"]["allForPermissions"][number]; +type EnvironmentForPermissions = ProjectForPermissions["environments"][number]; + +type Project = ProjectForPermissions; +type Environment = EnvironmentForPermissions; export type Services = { appName: string; @@ -173,7 +177,9 @@ interface Props { export const AddUserPermissions = ({ userId }: Props) => { const [isOpen, setIsOpen] = useState(false); - const { data: projects } = api.project.all.useQuery(); + const { data: projects } = api.project.allForPermissions.useQuery(undefined, { + enabled: isOpen, + }); const { data, refetch } = api.user.one.useQuery( { diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index 3c9cc23cc..395530c43 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -37,7 +37,11 @@ import { TRPCError } from "@trpc/server"; import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import type { AnyPgColumn } from "drizzle-orm/pg-core"; import { z } from "zod"; -import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { + adminProcedure, + createTRPCRouter, + protectedProcedure, +} from "@/server/api/trpc"; import { apiCreateProject, apiFindOneProject, @@ -332,6 +336,106 @@ export const projectRouter = createTRPCRouter({ }); }), + /** All projects with full environments and services for the admin permissions UI. Admin only. */ + allForPermissions: adminProcedure.query(async ({ ctx }) => { + return await db.query.projects.findMany({ + where: eq(projects.organizationId, ctx.session.activeOrganizationId), + orderBy: desc(projects.createdAt), + columns: { + projectId: true, + name: true, + }, + with: { + environments: { + columns: { + environmentId: true, + name: true, + isDefault: true, + }, + with: { + applications: { + columns: { + applicationId: true, + appName: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, + }, + }, + mariadb: { + columns: { + mariadbId: true, + appName: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, + }, + }, + postgres: { + columns: { + postgresId: true, + appName: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, + }, + }, + mysql: { + columns: { + mysqlId: true, + appName: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, + }, + }, + mongo: { + columns: { + mongoId: true, + appName: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, + }, + }, + redis: { + columns: { + redisId: true, + appName: true, + name: true, + createdAt: true, + applicationStatus: true, + description: true, + serverId: true, + }, + }, + compose: { + columns: { + composeId: true, + appName: true, + name: true, + createdAt: true, + composeStatus: true, + description: true, + serverId: true, + }, + }, + }, + }, + }, + }); + }), + search: protectedProcedure .input( z.object({ From 612e73bb80aa269414f197c10a50e0d76a45adcd Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:02:48 +0000 Subject: [PATCH 5/8] [autofix.ci] apply automated fixes --- .../components/dashboard/settings/users/add-permissions.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx index 6fff4e80c..eb3ca3f7a 100644 --- a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx +++ b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx @@ -29,7 +29,8 @@ import { Switch } from "@/components/ui/switch"; import { api, type RouterOutputs } from "@/utils/api"; /** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */ -type ProjectForPermissions = RouterOutputs["project"]["allForPermissions"][number]; +type ProjectForPermissions = + RouterOutputs["project"]["allForPermissions"][number]; type EnvironmentForPermissions = ProjectForPermissions["environments"][number]; type Project = ProjectForPermissions; From 7da69862e1d965828d16fc22e9c0d716383c815a Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 1 Mar 2026 14:07:16 -0600 Subject: [PATCH 6/8] refactor: update project query to use permissions-aware endpoint Replaced the existing project query with the new `allForPermissions` endpoint to enhance data retrieval for server monitoring settings. This change aligns with recent API enhancements aimed at improving permissions management. --- .../components/dashboard/settings/servers/setup-monitoring.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-monitoring.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-monitoring.tsx index 6468ce25d..701593436 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-monitoring.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-monitoring.tsx @@ -100,7 +100,7 @@ export const SetupMonitoring = ({ serverId }: Props) => { const url = useUrl(); - const { data: projects } = api.project.all.useQuery(); + const { data: projects } = api.project.allForPermissions.useQuery(); const extractServicesFromProjects = () => { if (!projects) return []; From 6c1f2372ed6aa203d1f89bfcc6487bf3501b3ca6 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 1 Mar 2026 14:15:47 -0600 Subject: [PATCH 7/8] refactor: clean up project dashboard and API response structure Removed unused imports and redundant code in the project dashboard component to enhance readability. Updated the API project router to streamline the data structure by eliminating unnecessary domain retrievals, while ensuring essential application and compose details are still included. This refactor improves maintainability and optimizes data handling for the project management interface. --- .../components/dashboard/projects/show.tsx | 128 ------------------ .../settings/users/add-permissions.tsx | 1 - apps/dokploy/server/api/routers/project.ts | 10 +- 3 files changed, 4 insertions(+), 135 deletions(-) diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index c3d4d498b..f25fb6d47 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -2,7 +2,6 @@ import { AlertTriangle, ArrowUpDown, BookIcon, - ExternalLinkIcon, FolderInput, Loader2, MoreHorizontalIcon, @@ -16,7 +15,6 @@ import { toast } from "sonner"; import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input"; -import { StatusTooltip } from "@/components/shared/status-tooltip"; import { AlertDialog, AlertDialogAction, @@ -40,10 +38,8 @@ import { import { DropdownMenu, DropdownMenuContent, - DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, - DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { @@ -280,14 +276,6 @@ export const ShowProjects = () => { ) .reduce((acc, curr) => acc + curr, 0); - const haveServicesWithDomains = project?.environments - .map( - (env) => - env.applications.length > 0 || - env.compose.length > 0, - ) - .some(Boolean); - // Find default environment from accessible environments, or fall back to first accessible environment const accessibleEnvironment = project?.environments.find((env) => env.isDefault) || @@ -313,122 +301,6 @@ export const ShowProjects = () => { }} > - {haveServicesWithDomains ? ( - - - - - e.stopPropagation()} - > - {project.environments.some( - (env) => env.applications.length > 0, - ) && ( - - - Applications - - {project.environments.map((env) => - env.applications.map((app) => ( -
- - - - {app.name} - - - - {app.domains.map((domain) => ( - - - - {domain.host} - - - - - ))} - -
- )), - )} -
- )} - {project.environments.some( - (env) => env.compose.length > 0, - ) && ( - - - Compose - - {project.environments.map((env) => - env.compose.map((comp) => ( -
- - - - {comp.name} - - - - {comp.domains.map((domain) => ( - - - - {domain.host} - - - - - ))} - -
- )), - )} -
- )} -
-
- ) : null} diff --git a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx index eb3ca3f7a..d0a2a26fa 100644 --- a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx +++ b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx @@ -33,7 +33,6 @@ type ProjectForPermissions = RouterOutputs["project"]["allForPermissions"][number]; type EnvironmentForPermissions = ProjectForPermissions["environments"][number]; -type Project = ProjectForPermissions; type Environment = EnvironmentForPermissions; export type Services = { diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index 395530c43..b026b6d2c 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -284,11 +284,10 @@ export const projectRouter = createTRPCRouter({ environments: { with: { applications: { - with: { - domains: true, - }, columns: { applicationId: true, + name: true, + applicationStatus: true, }, }, mariadb: { @@ -317,11 +316,10 @@ export const projectRouter = createTRPCRouter({ }, }, compose: { - with: { - domains: true, - }, columns: { composeId: true, + name: true, + composeStatus: true, }, }, }, From cc3b902d1e359e8746285ead0f48f9bd7734003a Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 1 Mar 2026 14:20:08 -0600 Subject: [PATCH 8/8] feat: include project name in API response columns Added the 'name' column to the project API response structure to enhance the data returned for project queries. This change improves the clarity and usability of the API by ensuring that project names are included in the response, facilitating better data handling for clients. --- apps/dokploy/server/api/routers/project.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index b026b6d2c..e270ee4b4 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -223,27 +223,34 @@ export const projectRouter = createTRPCRouter({ applications.applicationId, accessedServices, ), - with: { domains: true }, columns: { applicationId: true, + name: true, + applicationStatus: true, }, }, mariadb: { where: buildServiceFilter(mariadb.mariadbId, accessedServices), columns: { mariadbId: true, + name: true, + applicationStatus: true, }, }, mongo: { where: buildServiceFilter(mongo.mongoId, accessedServices), columns: { mongoId: true, + name: true, + applicationStatus: true, }, }, mysql: { where: buildServiceFilter(mysql.mysqlId, accessedServices), columns: { mysqlId: true, + name: true, + applicationStatus: true, }, }, postgres: { @@ -253,25 +260,31 @@ export const projectRouter = createTRPCRouter({ ), columns: { postgresId: true, + name: true, + applicationStatus: true, }, }, redis: { where: buildServiceFilter(redis.redisId, accessedServices), columns: { redisId: true, + name: true, + applicationStatus: true, }, }, compose: { where: buildServiceFilter(compose.composeId, accessedServices), - with: { domains: true }, columns: { composeId: true, + name: true, + composeStatus: true, }, }, }, columns: { environmentId: true, isDefault: true, + name: true, }, }, }, @@ -324,6 +337,7 @@ export const projectRouter = createTRPCRouter({ }, }, columns: { + name: true, environmentId: true, isDefault: true, },