diff --git a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx index f99022d04..6092b9b85 100644 --- a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx +++ b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx @@ -19,11 +19,10 @@ import { } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { api } from "@/utils/api"; -import type { RouterOutputs } from "@/utils/api"; interface Props { - applicationId?: string; - composeId?: string; + id: string; + type: "application" | "compose"; repoPath: string; onClose: () => void; } @@ -35,12 +34,7 @@ type DirectoryEntry = { children?: DirectoryEntry[]; }; -export const PatchEditor = ({ - applicationId, - composeId, - repoPath, - onClose, -}: Props) => { +export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { const [selectedFile, setSelectedFile] = useState(null); const [fileContent, setFileContent] = useState(""); const [originalContent, setOriginalContent] = useState(""); @@ -49,14 +43,12 @@ export const PatchEditor = ({ ); const [isSaving, setIsSaving] = useState(false); - // Fetch directory tree const { data: directories, isLoading: isDirLoading } = api.patch.readRepoDirectories.useQuery( - { applicationId, composeId, repoPath }, + { id: id, type, repoPath }, { enabled: !!repoPath }, ); - // Save mutation const saveAsPatch = api.patch.saveFileAsPatch.useMutation({ onSuccess: (result) => { setIsSaving(false); @@ -77,8 +69,8 @@ export const PatchEditor = ({ const { data: fileData, isFetching: isFileLoading } = api.patch.readRepoFile.useQuery( { - applicationId, - composeId, + id: id || "", + type, repoPath, filePath: selectedFile || "", }, @@ -114,8 +106,8 @@ export const PatchEditor = ({ if (!selectedFile) return; setIsSaving(true); saveAsPatch.mutate({ - applicationId, - composeId, + id, + type, repoPath, filePath: selectedFile, content: fileContent, @@ -142,8 +134,11 @@ export const PatchEditor = ({ return (
- + {patches && patches?.length > 0 && ( + + )} {isPatchesLoading ? (
- ) : !patches || patches.length === 0 ? ( - - - No patches - - No patches have been created for this application yet. Click - "Create Patch" to add modifications to your code during build. - - + ) : patches?.length === 0 ? ( +
+
+ +
+
+

No patches yet

+

+ Add file patches to modify your repo before each build—configs, + env, or code. Create your first patch to get started. +

+
+ +
) : ( @@ -183,7 +147,7 @@ export const ShowPatches = ({ applicationId, composeId }: Props) => { - {patches.map((patch: Patch) => ( + {patches?.map((patch) => (
@@ -194,16 +158,49 @@ export const ShowPatches = ({ applicationId, composeId }: Props) => { - handleTogglePatch(patch.patchId, checked) - } + onCheckedChange={(checked) => { + togglePatch + .mutateAsync({ + patchId: patch.patchId, + enabled: checked, + }) + .then(() => { + toast.success("Patch updated"); + utils.patch.byApplicationId.invalidate({ + applicationId: id, + }); + utils.patch.byComposeId.invalidate({ + composeId: id, + }); + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(() => { + setIsLoadingRepo(false); + }); + }} /> diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx index c075bfa89..c17f92052 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx @@ -26,11 +26,11 @@ import { ShowDomains } from "@/components/dashboard/application/domains/show-dom import { ShowEnvironment } from "@/components/dashboard/application/environment/show"; import { ShowGeneralApplication } from "@/components/dashboard/application/general/show"; import { ShowDockerLogs } from "@/components/dashboard/application/logs/show"; +import { ShowPatches } from "@/components/dashboard/application/patches/show-patches"; import { ShowPreviewDeployments } from "@/components/dashboard/application/preview-deployments/show-preview-deployments"; import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules"; import { UpdateApplication } from "@/components/dashboard/application/update-application"; import { ShowVolumeBackups } from "@/components/dashboard/application/volume-backups/show-volume-backups"; -import { ShowPatches } from "@/components/dashboard/application/patches/show-patches"; import { DeleteService } from "@/components/dashboard/compose/delete-service"; import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring"; import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring"; @@ -365,7 +365,7 @@ const Service = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx index 5423bb86c..3b731ee08 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx @@ -17,9 +17,9 @@ import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes import { ShowDeployments } from "@/components/dashboard/application/deployments/show-deployments"; import { ShowDomains } from "@/components/dashboard/application/domains/show-domains"; import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment"; +import { ShowPatches } from "@/components/dashboard/application/patches/show-patches"; import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules"; import { ShowVolumeBackups } from "@/components/dashboard/application/volume-backups/show-volume-backups"; -import { ShowPatches } from "@/components/dashboard/application/patches/show-patches"; import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command"; import { IsolatedDeploymentTab } from "@/components/dashboard/compose/advanced/add-isolation"; import { DeleteService } from "@/components/dashboard/compose/delete-service"; @@ -367,7 +367,7 @@ const Service = (
- +
diff --git a/apps/dokploy/server/api/routers/patch.ts b/apps/dokploy/server/api/routers/patch.ts index f5c10946a..9deae22e4 100644 --- a/apps/dokploy/server/api/routers/patch.ts +++ b/apps/dokploy/server/api/routers/patch.ts @@ -6,10 +6,10 @@ import { ensurePatchRepo, findApplicationById, findComposeById, + findPatchByFilePath, findPatchById, findPatchesByApplicationId, findPatchesByComposeId, - findPatchByFilePath, generatePatch, readPatchRepoDirectory, readPatchRepoFile, @@ -218,13 +218,13 @@ export const patchRouter = createTRPCRouter({ ensureRepo: protectedProcedure .input( z.object({ - applicationId: z.string().optional(), - composeId: z.string().optional(), + id: z.string(), + type: z.enum(["application", "compose"]), }), ) .mutation(async ({ input, ctx }) => { - if (input.applicationId) { - const app = await findApplicationById(input.applicationId); + if (input.type === "application") { + const app = await findApplicationById(input.id); if ( app.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -253,8 +253,8 @@ export const patchRouter = createTRPCRouter({ }); } - if (input.composeId) { - const compose = await findComposeById(input.composeId); + if (input.type === "compose") { + const compose = await findComposeById(input.id); if ( compose.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -285,21 +285,21 @@ export const patchRouter = createTRPCRouter({ throw new TRPCError({ code: "BAD_REQUEST", - message: "Either applicationId or composeId must be provided", + message: "Either application or compose must be provided", }); }), readRepoDirectories: protectedProcedure .input( z.object({ - applicationId: z.string().optional(), - composeId: z.string().optional(), + id: z.string(), + type: z.enum(["application", "compose"]), repoPath: z.string(), }), ) .query(async ({ input, ctx }) => { - if (input.applicationId) { - const app = await findApplicationById(input.applicationId); + if (input.type === "application") { + const app = await findApplicationById(input.id); if ( app.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -312,8 +312,8 @@ export const patchRouter = createTRPCRouter({ return await readPatchRepoDirectory(input.repoPath, app.serverId); } - if (input.composeId) { - const compose = await findComposeById(input.composeId); + if (input.type === "compose") { + const compose = await findComposeById(input.id); if ( compose.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -328,15 +328,15 @@ export const patchRouter = createTRPCRouter({ throw new TRPCError({ code: "BAD_REQUEST", - message: "Either applicationId or composeId must be provided", + message: "Either application or compose must be provided", }); }), readRepoFile: protectedProcedure .input( z.object({ - applicationId: z.string().optional(), - composeId: z.string().optional(), + id: z.string(), + type: z.enum(["application", "compose"]), repoPath: z.string(), filePath: z.string(), }), @@ -345,8 +345,8 @@ export const patchRouter = createTRPCRouter({ let serverId: string | null = null; let patchContent: string | undefined; - if (input.applicationId) { - const app = await findApplicationById(input.applicationId); + if (input.type === "application") { + const app = await findApplicationById(input.id); if ( app.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -361,14 +361,14 @@ export const patchRouter = createTRPCRouter({ // Check if patch exists for this file const existingPatch = await findPatchByFilePath( input.filePath, - input.applicationId, + input.id, undefined, ); if (existingPatch?.enabled) { patchContent = existingPatch.content; } - } else if (input.composeId) { - const compose = await findComposeById(input.composeId); + } else if (input.type === "compose") { + const compose = await findComposeById(input.id); if ( compose.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -384,7 +384,7 @@ export const patchRouter = createTRPCRouter({ const existingPatch = await findPatchByFilePath( input.filePath, undefined, - input.composeId, + input.id, ); if (existingPatch?.enabled) { patchContent = existingPatch.content; @@ -407,8 +407,8 @@ export const patchRouter = createTRPCRouter({ saveFileAsPatch: protectedProcedure .input( z.object({ - applicationId: z.string().optional(), - composeId: z.string().optional(), + id: z.string(), + type: z.enum(["application", "compose"]), repoPath: z.string(), filePath: z.string(), content: z.string(), @@ -417,8 +417,8 @@ export const patchRouter = createTRPCRouter({ .mutation(async ({ input, ctx }) => { let serverId: string | null = null; - if (input.applicationId) { - const app = await findApplicationById(input.applicationId); + if (input.type === "application") { + const app = await findApplicationById(input.id); if ( app.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -429,8 +429,8 @@ export const patchRouter = createTRPCRouter({ }); } serverId = app.serverId; - } else if (input.composeId) { - const compose = await findComposeById(input.composeId); + } else if (input.type === "compose") { + const compose = await findComposeById(input.id); if ( compose.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -444,7 +444,7 @@ export const patchRouter = createTRPCRouter({ } else { throw new TRPCError({ code: "BAD_REQUEST", - message: "Either applicationId or composeId must be provided", + message: "Either application or compose must be provided", }); } @@ -460,8 +460,8 @@ export const patchRouter = createTRPCRouter({ // No changes - remove existing patch if any const existingPatch = await findPatchByFilePath( input.filePath, - input.applicationId, - input.composeId, + input.id, + input.id, ); if (existingPatch) { await deletePatch(existingPatch.patchId); @@ -472,8 +472,8 @@ export const patchRouter = createTRPCRouter({ // Check if patch exists const existingPatch = await findPatchByFilePath( input.filePath, - input.applicationId, - input.composeId, + input.id, + input.id, ); if (existingPatch) { @@ -487,8 +487,8 @@ export const patchRouter = createTRPCRouter({ filePath: input.filePath, content: patchContent, enabled: true, - applicationId: input.applicationId, - composeId: input.composeId, + applicationId: input.id, + composeId: input.id, }); return { deleted: false, patchId: newPatch.patchId }; diff --git a/packages/server/src/services/patch-repo.ts b/packages/server/src/services/patch-repo.ts index c4aee3b68..98ac201a0 100644 --- a/packages/server/src/services/patch-repo.ts +++ b/packages/server/src/services/patch-repo.ts @@ -132,6 +132,8 @@ export const readPatchRepoDirectory = async ( // Use git ls-tree to get tracked files only const command = `cd "${repoPath}" && git ls-tree -r --name-only HEAD`; + console.log("command", command); + let stdout: string; try { if (serverId) {