mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-04 05:25:22 +02:00
refactor(patch-editor): streamline patch saving and loading logic
- Removed the local state for saving status and integrated loading state directly from the mutation hook. - Updated the saveAsPatch function to handle success and error notifications more cleanly. - Adjusted the file content loading to ensure it retrieves the latest data, improving user experience and consistency in the PatchEditor component. - Refactored API calls in the patch router to unify patch retrieval logic for applications and composes, enhancing maintainability.
This commit is contained in:
@@ -41,7 +41,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
|
||||
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const { data: directories, isLoading: isDirLoading } =
|
||||
api.patch.readRepoDirectories.useQuery(
|
||||
@@ -49,27 +48,13 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
|
||||
{ enabled: !!repoPath },
|
||||
);
|
||||
|
||||
const saveAsPatch = api.patch.saveFileAsPatch.useMutation({
|
||||
onSuccess: (result) => {
|
||||
setIsSaving(false);
|
||||
if (result.deleted) {
|
||||
toast.success("No changes - patch removed");
|
||||
} else {
|
||||
toast.success("Patch saved");
|
||||
}
|
||||
setOriginalContent(fileContent);
|
||||
},
|
||||
onError: () => {
|
||||
setIsSaving(false);
|
||||
toast.error("Failed to save patch");
|
||||
},
|
||||
});
|
||||
const { mutateAsync: saveAsPatch, isLoading: isSavingPatch } =
|
||||
api.patch.saveFileAsPatch.useMutation();
|
||||
|
||||
// Read file content when selected
|
||||
const { data: fileData, isFetching: isFileLoading } =
|
||||
api.patch.readRepoFile.useQuery(
|
||||
{
|
||||
id: id || "",
|
||||
id,
|
||||
type,
|
||||
repoPath,
|
||||
filePath: selectedFile || "",
|
||||
@@ -77,8 +62,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
|
||||
{
|
||||
enabled: !!selectedFile,
|
||||
onSuccess: (data) => {
|
||||
setFileContent(data.content);
|
||||
setOriginalContent(data.content);
|
||||
if (data.patchError) {
|
||||
toast.error(data.patchErrorMessage || "Failed to apply patch");
|
||||
}
|
||||
@@ -104,14 +87,19 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
|
||||
|
||||
const handleSave = () => {
|
||||
if (!selectedFile) return;
|
||||
setIsSaving(true);
|
||||
saveAsPatch.mutate({
|
||||
saveAsPatch({
|
||||
id,
|
||||
type,
|
||||
repoPath,
|
||||
filePath: selectedFile,
|
||||
content: fileContent,
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Patch saved");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Failed to save patch");
|
||||
});
|
||||
};
|
||||
|
||||
const hasChanges = fileContent !== originalContent;
|
||||
@@ -192,8 +180,8 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
{selectedFile && (
|
||||
<Button onClick={handleSave} disabled={isSaving || !hasChanges}>
|
||||
{isSaving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
<Button onClick={handleSave} disabled={isSavingPatch || !hasChanges}>
|
||||
{isSavingPatch && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
Save Patch
|
||||
</Button>
|
||||
@@ -201,7 +189,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="grid grid-cols-[250px_1fr] border-t h-[600px]">
|
||||
{/* File Tree */}
|
||||
<div className="border-r h-full overflow-hidden">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-2">
|
||||
@@ -219,7 +206,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
{/* Editor */}
|
||||
<div className="h-full overflow-hidden relative">
|
||||
{isFileLoading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
@@ -227,7 +213,7 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
|
||||
</div>
|
||||
) : selectedFile ? (
|
||||
<CodeEditor
|
||||
value={fileContent}
|
||||
value={fileData?.content || ""}
|
||||
onChange={(value) => setFileContent(value || "")}
|
||||
className="h-full w-full"
|
||||
wrapperClassName="h-full"
|
||||
|
||||
@@ -51,8 +51,8 @@ const getApplicationGitConfig = (
|
||||
};
|
||||
case "gitea":
|
||||
return {
|
||||
gitUrl: app.gitea?.gitUrl
|
||||
? `${app.gitea.gitUrl}/${app.giteaOwner}/${app.giteaRepository}.git`
|
||||
gitUrl: app.gitea?.giteaUrl
|
||||
? `${app.gitea.giteaUrl}/${app.giteaOwner}/${app.giteaRepository}.git`
|
||||
: "",
|
||||
gitBranch: app.giteaBranch || "main",
|
||||
sshKeyId: null,
|
||||
@@ -93,8 +93,8 @@ const getComposeGitConfig = (
|
||||
};
|
||||
case "gitea":
|
||||
return {
|
||||
gitUrl: compose.gitea?.gitUrl
|
||||
? `${compose.gitea.gitUrl}/${compose.giteaOwner}/${compose.giteaRepository}.git`
|
||||
gitUrl: compose.gitea?.giteaUrl
|
||||
? `${compose.gitea.giteaUrl}/${compose.giteaOwner}/${compose.giteaRepository}.git`
|
||||
: "",
|
||||
gitBranch: compose.giteaBranch || "main",
|
||||
sshKeyId: null,
|
||||
@@ -343,7 +343,6 @@ export const patchRouter = createTRPCRouter({
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
let serverId: string | null = null;
|
||||
let patchContent: string | undefined;
|
||||
|
||||
if (input.type === "application") {
|
||||
const app = await findApplicationById(input.id);
|
||||
@@ -357,16 +356,6 @@ export const patchRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
serverId = app.serverId;
|
||||
|
||||
// Check if patch exists for this file
|
||||
const existingPatch = await findPatchByFilePath(
|
||||
input.filePath,
|
||||
input.id,
|
||||
undefined,
|
||||
);
|
||||
if (existingPatch?.enabled) {
|
||||
patchContent = existingPatch.content;
|
||||
}
|
||||
} else if (input.type === "compose") {
|
||||
const compose = await findComposeById(input.id);
|
||||
if (
|
||||
@@ -379,27 +368,22 @@ export const patchRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
serverId = compose.serverId;
|
||||
|
||||
// Check if patch exists for this file
|
||||
const existingPatch = await findPatchByFilePath(
|
||||
input.filePath,
|
||||
undefined,
|
||||
input.id,
|
||||
);
|
||||
if (existingPatch?.enabled) {
|
||||
patchContent = existingPatch.content;
|
||||
}
|
||||
} else {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Either applicationId or composeId must be provided",
|
||||
});
|
||||
}
|
||||
const existingPatch = await findPatchByFilePath(
|
||||
input.filePath,
|
||||
input.id,
|
||||
input.type,
|
||||
);
|
||||
|
||||
return await readPatchRepoFile(
|
||||
input.repoPath,
|
||||
input.filePath,
|
||||
patchContent,
|
||||
existingPatch?.enabled ? existingPatch?.content : undefined,
|
||||
serverId,
|
||||
);
|
||||
}),
|
||||
@@ -461,7 +445,7 @@ export const patchRouter = createTRPCRouter({
|
||||
const existingPatch = await findPatchByFilePath(
|
||||
input.filePath,
|
||||
input.id,
|
||||
input.id,
|
||||
input.type,
|
||||
);
|
||||
if (existingPatch) {
|
||||
await deletePatch(existingPatch.patchId);
|
||||
@@ -473,22 +457,20 @@ export const patchRouter = createTRPCRouter({
|
||||
const existingPatch = await findPatchByFilePath(
|
||||
input.filePath,
|
||||
input.id,
|
||||
input.id,
|
||||
input.type,
|
||||
);
|
||||
|
||||
if (existingPatch) {
|
||||
// Update existing patch
|
||||
await updatePatch(existingPatch.patchId, { content: patchContent });
|
||||
return { deleted: false, patchId: existingPatch.patchId };
|
||||
}
|
||||
|
||||
// Create new patch
|
||||
const newPatch = await createPatch({
|
||||
filePath: input.filePath,
|
||||
content: patchContent,
|
||||
enabled: true,
|
||||
applicationId: input.id,
|
||||
composeId: input.id,
|
||||
applicationId: input.type === "application" ? input.id : undefined,
|
||||
composeId: input.type === "compose" ? input.id : undefined,
|
||||
});
|
||||
|
||||
return { deleted: false, patchId: newPatch.patchId };
|
||||
|
||||
@@ -76,23 +76,15 @@ export const findPatchesByComposeId = async (composeId: string) => {
|
||||
|
||||
export const findPatchByFilePath = async (
|
||||
filePath: string,
|
||||
applicationId?: string,
|
||||
composeId?: string,
|
||||
id: string,
|
||||
type: "application" | "compose",
|
||||
) => {
|
||||
if (applicationId) {
|
||||
return await db.query.patch.findFirst({
|
||||
where: and(
|
||||
eq(patch.filePath, filePath),
|
||||
eq(patch.applicationId, applicationId),
|
||||
),
|
||||
});
|
||||
}
|
||||
if (composeId) {
|
||||
return await db.query.patch.findFirst({
|
||||
where: and(eq(patch.filePath, filePath), eq(patch.composeId, composeId)),
|
||||
});
|
||||
}
|
||||
return null;
|
||||
return await db.query.patch.findFirst({
|
||||
where:
|
||||
type === "application"
|
||||
? and(eq(patch.filePath, filePath), eq(patch.applicationId, id))
|
||||
: and(eq(patch.filePath, filePath), eq(patch.composeId, id)),
|
||||
});
|
||||
};
|
||||
|
||||
export const updatePatch = async (patchId: string, data: Partial<Patch>) => {
|
||||
|
||||
Reference in New Issue
Block a user