Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ce9ba60
impl
ivanpadavan Jan 30, 2026
a4eb0bf
migration
ivanpadavan Feb 10, 2026
7747929
Remove unused SQL migration and associated journal entries for "0143_…
Siumauricio Feb 17, 2026
13b94ed
Merge branch 'canary' into patches-impl
Siumauricio Feb 17, 2026
0fc043d
feat(database): add "patch" table with foreign key constraints and cr…
Siumauricio Feb 17, 2026
752f90c
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 17, 2026
88f387d
refactor(patches): unify patch handling for applications and composes
Siumauricio Feb 17, 2026
2032063
refactor(patch-editor): streamline patch saving and loading logic
Siumauricio Feb 17, 2026
9818e3c
refactor(patch): simplify patch repository management and enhance git…
Siumauricio Feb 17, 2026
46ac272
refactor(patch): unify patch retrieval and application logic
Siumauricio Feb 17, 2026
1c25ab4
feat(patch-dialog): add EditPatchDialog component for editing patches
Siumauricio Feb 17, 2026
c89f2e3
refactor(patch-editor): remove repoPath dependency and streamline pat…
Siumauricio Feb 17, 2026
2db4c44
refactor(patch): optimize patch application command generation
Siumauricio Feb 17, 2026
9eeac50
feat(patch): add patchType enum and update patch schema
Siumauricio Feb 17, 2026
8aba7b0
feat(patch): implement CreateFileDialog and integrate file creation i…
Siumauricio Feb 17, 2026
46e1bed
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 17, 2026
6350a8d
refactor(compose): streamline command generation in deployCompose fun…
Siumauricio Feb 17, 2026
8315845
Merge branch 'canary' into patches-impl
Siumauricio Feb 18, 2026
df2221a
refactor(patch): enforce minimum length for ID input and streamline r…
Siumauricio Feb 18, 2026
ef65e09
Merge branch 'canary' into patches-impl
Siumauricio Feb 18, 2026
309a411
delete: remove integration tests for patch generation and application
Siumauricio Feb 18, 2026
42e8320
Merge branch 'canary' into patches-impl
Siumauricio Feb 18, 2026
5faa319
feat: add patch table and related constraints
Siumauricio Feb 18, 2026
97f9e8a
test: enhance application tests with patch mock and additional paths
Siumauricio Feb 18, 2026
3439b75
test: add mock for patch table in application.real.test.ts
Siumauricio Feb 18, 2026
5ef431b
test: update mock implementation in application.real.test.ts
Siumauricio Feb 18, 2026
20acc8b
test: remove unused patch mock and deployment test in application.rea…
Siumauricio Feb 18, 2026
d99e0bf
test: remove unused mock constants in application.real.test.ts
Siumauricio Feb 18, 2026
15a1a5d
test: remove unused mock for patch service in application.real.test.ts
Siumauricio Feb 18, 2026
f7079f5
test: remove unused mock for patch service in application.command.tes…
Siumauricio Feb 18, 2026
0d4d609
test: add mock for patch service in application.command.test.ts and a…
Siumauricio Feb 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/dokploy/__test__/deploy/application.command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ vi.mock("@dokploy/server/db", () => {
applications: {
findFirst: vi.fn(),
},
patch: {
findMany: vi.fn().mockResolvedValue([]),
},
},
},
};
Expand Down
3 changes: 3 additions & 0 deletions apps/dokploy/__test__/deploy/application.real.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ vi.mock("@dokploy/server/db", () => {
applications: {
findFirst: vi.fn(),
},
patch: {
findMany: vi.fn().mockResolvedValue([]),
},
},
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { FilePlus } from "lucide-react";
import { useState } from "react";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

interface Props {
folderPath: string;
onCreate: (filename: string, content: string) => void;
onOpenChange: (open: boolean) => void;
alwaysVisible?: boolean;
}

export const CreateFileDialog = ({
folderPath,
onCreate,
onOpenChange,
alwaysVisible = false,
}: Props) => {
const [filename, setFilename] = useState("");
const [content, setContent] = useState("");

const handleCreate = () => {
if (!filename.trim()) return;
onCreate(filename.trim(), content);
setFilename("");
setContent("");
onOpenChange(false);
};

return (
<Dialog>
<DialogTrigger asChild>
<Button
variant="ghost"
size="icon"
type="button"
className={`h-6 w-6 ${alwaysVisible ? "" : "opacity-0 group-hover:opacity-100"}`}
title="Create file"
>
<FilePlus className="h-3 w-3" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-2xl">
<form
onSubmit={(e) => {
e.preventDefault();
handleCreate();
}}
>
<DialogHeader>
<DialogTitle>Create file</DialogTitle>
<DialogDescription>
{folderPath ? `New file in ${folderPath}/` : "New file in root"}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="filename">Filename</Label>
<Input
id="filename"
placeholder="e.g. .env.example"
value={filename}
onChange={(e) => setFilename(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label>Content</Label>
<div className="h-[200px] rounded-md border">
<CodeEditor
value={content}
onChange={(v) => setContent(v ?? "")}
className="h-full"
wrapperClassName="h-[200px]"
lineWrapping
/>
</div>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline" type="button">
Cancel
</Button>
</DialogClose>
<DialogClose asChild>
<Button type="submit" disabled={!filename.trim()}>
Create
</Button>
</DialogClose>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Loader2, Pencil } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { api } from "@/utils/api";

interface Props {
patchId: string;
entityId: string;
type: "application" | "compose";
onSuccess?: () => void;
}

export const EditPatchDialog = ({
patchId,
entityId,
type,
onSuccess,
}: Props) => {
const { data: patch, isLoading: isPatchLoading } = api.patch.one.useQuery(
{ patchId },
{ enabled: !!patchId },
);
const [content, setContent] = useState("");

useEffect(() => {
if (patch) {
setContent(patch.content);
}
}, [patch]);

const utils = api.useUtils();
const updatePatch = api.patch.update.useMutation();

const handleSave = () => {
updatePatch
.mutateAsync({ patchId, content })
.then(() => {
toast.success("Patch saved");
utils.patch.byEntityId.invalidate({ id: entityId, type });
onSuccess?.();
})
.catch((err) => {
toast.error(err.message);
});
};

return (
<Dialog>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" title="Edit patch">
<Pencil className="h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-4xl max-h-[85vh] flex flex-col p-0">
<DialogHeader className="px-6 pt-6 pb-4">
<DialogTitle>Edit Patch</DialogTitle>
<DialogDescription>
{patch ? `Editing: ${patch.filePath}` : "Loading patch..."}
</DialogDescription>
</DialogHeader>
{isPatchLoading ? (
<div className="flex flex-1 items-center justify-center px-6 py-12">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : (
<div className="flex-1 min-h-0 px-6 overflow-hidden flex flex-col">
<CodeEditor
value={content}
onChange={(value) => setContent(value ?? "")}
className="h-[400px] w-full"
wrapperClassName="h-[400px]"
lineWrapping
/>
</div>
)}
<DialogFooter className="px-6 ">
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button onClick={handleSave} isLoading={updatePatch.isLoading}>
{updatePatch.isPending && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Save
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./show-patches";
export * from "./patch-editor";
Loading