Compare commits

...

19 Commits

Author SHA1 Message Date
Mauricio Siu
5992688e85 Merge pull request #1909 from Dokploy/1868-backups-failing-due-to-a-directory-not-empty-error
refactor: improve cleanup process in web server backup utility to han…
2025-05-17 00:21:58 -06:00
Mauricio Siu
425061e481 refactor: improve cleanup process in web server backup utility to handle errors during temporary directory removal 2025-05-17 00:20:54 -06:00
Mauricio Siu
08c0bf8a21 Merge pull request #1908 from Dokploy/1863-pg_dump-backup-fails-stdout-maxbuffer-length-exceeded
refactor: update database backup process in web server utility to use…
2025-05-17 00:14:59 -06:00
Mauricio Siu
64a2c9e0a1 refactor: update database backup process in web server utility to use temporary file in container 2025-05-17 00:13:43 -06:00
Mauricio Siu
21e46f5382 Merge pull request #1907 from Dokploy/1878-dokploy-application-settings-not-linked-resulting-in-progress-lost-on-save
fix: update dependencies in save provider components to use optional …
2025-05-16 23:23:20 -06:00
autofix-ci[bot]
52b2158309 [autofix.ci] apply automated fixes 2025-05-17 05:22:55 +00:00
Mauricio Siu
178d84d438 fix: update dependencies in save provider components to use optional chaining for applicationId and composeId 2025-05-16 23:22:26 -06:00
Mauricio Siu
80016b57a8 Merge pull request #1906 from Dokploy/1888-docker-compose-preview-is-null
feat(ui): add loading state and no data message to converted compose …
2025-05-16 23:16:29 -06:00
Mauricio Siu
b4b2d12f6e feat(ui): add loading state and no data message to converted compose display 2025-05-16 23:16:02 -06:00
Mauricio Siu
294378d95b Merge pull request #1886 from oshanavishkapiries/canary
fix: Submit Log in issue on Github
2025-05-16 23:03:50 -06:00
Mauricio Siu
c52812f9d3 Merge pull request #1903 from yergom/fix/watch-path-wording
fix: more informative placeholder for watch path
2025-05-16 23:03:13 -06:00
Mauricio Siu
82f7c5d5f3 Merge pull request #1904 from darena-patrick/fix/compose-deploy-url
fix: Missing `/compose` in auto-deploy URL
2025-05-16 23:00:47 -06:00
Mauricio Siu
3d2ae52259 Merge pull request #1891 from nktnet1/fix-domain-responsiveness
Fix domain responsiveness
2025-05-16 22:59:41 -06:00
Patrick Schiess
bf115c7895 fix: Missing /compose in auto-deploy URL 2025-05-16 16:12:09 -06:00
yergom
c2c29dbaba fix: more informative placeholder for watch path 2025-05-16 13:25:28 +00:00
Tam Nguyen
136570b36c fix(ui): compose grid responsiveness starts from xl instead of lg 2025-05-13 16:01:25 +10:00
Tam Nguyen
7d0075c230 fix(ui): domain responsiveness with screen width 2025-05-13 15:43:52 +10:00
Tam Nguyen
19b4edee8d refactor: remove redundant class bg-card due to bg-transparent set later 2025-05-13 15:15:54 +10:00
153918928+oshanavishkapiries@users.noreply.github.com
7f04eb856e fix: Submit Log in issue on Github 2025-05-13 01:56:55 +05:30
17 changed files with 119 additions and 86 deletions

View File

@@ -86,7 +86,7 @@ export const ShowDeployments = ({
<span>Webhook URL: </span>
<div className="flex flex-row items-center gap-2">
<span className="break-all text-muted-foreground">
{`${url}/api/deploy/${refreshToken}`}
{`${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`}
</span>
{(type === "application" || type === "compose") && (
<RefreshToken id={id} type={type} />

View File

@@ -186,30 +186,19 @@ export const ShowDomains = ({ id, type }: Props) => {
return (
<Card
key={item.domainId}
className="relative overflow-hidden w-full border bg-card transition-all hover:shadow-md bg-transparent h-fit"
className="relative overflow-hidden w-full border transition-all hover:shadow-md bg-transparent h-fit"
>
<CardContent className="p-6">
<div className="flex flex-col gap-4">
{/* Service & Domain Info */}
<div className="flex items-start justify-between">
<div className="flex flex-col gap-2">
{item.serviceName && (
<Badge variant="outline" className="w-fit">
<Server className="size-3 mr-1" />
{item.serviceName}
</Badge>
)}
<Link
className="flex items-center gap-2 text-base font-medium hover:underline"
target="_blank"
href={`${item.https ? "https" : "http"}://${item.host}${item.path}`}
>
{item.host}
<ExternalLink className="size-4" />
</Link>
</div>
<div className="flex gap-2">
<div className="flex items-center justify-between flex-wrap gap-y-2">
{item.serviceName && (
<Badge variant="outline" className="w-fit">
<Server className="size-3 mr-1" />
{item.serviceName}
</Badge>
)}
<div className="flex gap-2 flex-wrap">
{!item.host.includes("traefik.me") && (
<DnsHelperModal
domain={{
@@ -266,6 +255,16 @@ export const ShowDomains = ({ id, type }: Props) => {
</DialogAction>
</div>
</div>
<div className="w-full break-all">
<Link
className="flex items-center gap-2 text-base font-medium hover:underline"
target="_blank"
href={`${item.https ? "https" : "http"}://${item.host}${item.path}`}
>
{item.host}
<ExternalLink className="size-4 min-w-4" />
</Link>
</div>
{/* Domain Details */}
<div className="flex flex-wrap gap-3">

View File

@@ -136,7 +136,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
enableSubmodules: data.enableSubmodules || false,
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.applicationId, form]);
const onSubmit = async (data: BitbucketProvider) => {
await mutateAsync({
@@ -435,7 +435,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@@ -454,7 +454,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
variant="secondary"
onClick={() => {
const input = document.querySelector(
'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]',
'input[placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"]',
) as HTMLInputElement;
const value = input.value.trim();
if (value) {

View File

@@ -53,7 +53,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
registryURL: data.registryUrl || "",
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.applicationId, form]);
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({

View File

@@ -262,7 +262,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@@ -281,7 +281,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
variant="secondary"
onClick={() => {
const input = document.querySelector(
'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]',
'input[placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"]',
) as HTMLInputElement;
const value = input.value.trim();
if (value) {

View File

@@ -158,7 +158,7 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
enableSubmodules: data.enableSubmodules || false,
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.applicationId, form]);
const onSubmit = async (data: GiteaProvider) => {
await mutateAsync({
@@ -470,7 +470,7 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
<div className="flex gap-2">
<FormControl>
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();

View File

@@ -134,7 +134,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false,
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.applicationId, form]);
const onSubmit = async (data: GithubProvider) => {
await mutateAsync({
@@ -474,7 +474,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
<div className="flex gap-2">
<FormControl>
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();

View File

@@ -141,7 +141,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false,
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.applicationId, form]);
const onSubmit = async (data: GitlabProvider) => {
await mutateAsync({
@@ -452,7 +452,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
<div className="flex gap-2">
<FormControl>
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();

View File

@@ -136,7 +136,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false,
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.composeId, form]);
const onSubmit = async (data: BitbucketProvider) => {
await mutateAsync({
@@ -437,7 +437,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@@ -456,7 +456,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
variant="secondary"
onClick={() => {
const input = document.querySelector(
'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]',
'input[placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"]',
) as HTMLInputElement;
const value = input.value.trim();
if (value) {

View File

@@ -263,7 +263,7 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@@ -282,7 +282,7 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
variant="secondary"
onClick={() => {
const input = document.querySelector(
'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]',
'input[placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"]',
) as HTMLInputElement;
const value = input.value.trim();
if (value) {

View File

@@ -142,7 +142,7 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false,
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.composeId, form]);
const onSubmit = async (data: GiteaProvider) => {
await mutateAsync({
@@ -437,7 +437,7 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => {
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();

View File

@@ -134,7 +134,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false,
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.composeId, form]);
const onSubmit = async (data: GithubProvider) => {
await mutateAsync({
@@ -474,7 +474,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@@ -496,7 +496,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
variant="secondary"
onClick={() => {
const input = document.querySelector(
'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]',
'input[placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"]',
) as HTMLInputElement;
const value = input.value.trim();
if (value) {

View File

@@ -142,7 +142,7 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false,
});
}
}, [form.reset, data, form]);
}, [form.reset, data?.composeId, form]);
const onSubmit = async (data: GitlabProvider) => {
await mutateAsync({
@@ -453,7 +453,7 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@@ -472,7 +472,7 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
variant="secondary"
onClick={() => {
const input = document.querySelector(
'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]',
'input[placeholder="Enter a path to watch (e.g., src/**, dist/*.js)"]',
) as HTMLInputElement;
const value = input.value.trim();
if (value) {

View File

@@ -10,7 +10,7 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { api } from "@/utils/api";
import { Puzzle, RefreshCw } from "lucide-react";
import { Loader2, Puzzle, RefreshCw } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
@@ -66,36 +66,50 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
Preview your docker-compose file with added domains. Note: At least
one domain must be specified for this conversion to take effect.
</AlertBlock>
{isLoading ? (
<div className="flex flex-row items-center justify-center min-h-[25rem] border p-4 rounded-md">
<Loader2 className="h-8 w-8 text-muted-foreground mb-2 animate-spin" />
</div>
) : compose?.length === 5 ? (
<div className="border p-4 rounded-md flex flex-col items-center justify-center min-h-[25rem]">
<Puzzle className="h-8 w-8 text-muted-foreground mb-2" />
<span className="text-muted-foreground">
No converted compose data available.
</span>
</div>
) : (
<>
<div className="flex flex-row gap-2 justify-end">
<Button
variant="secondary"
isLoading={isLoading}
onClick={() => {
mutateAsync({ composeId })
.then(() => {
refetch();
toast.success("Fetched source type");
})
.catch((err) => {
toast.error("Error fetching source type", {
description: err.message,
});
});
}}
>
Refresh <RefreshCw className="ml-2 h-4 w-4" />
</Button>
</div>
<div className="flex flex-row gap-2 justify-end">
<Button
variant="secondary"
isLoading={isLoading}
onClick={() => {
mutateAsync({ composeId })
.then(() => {
refetch();
toast.success("Fetched source type");
})
.catch((err) => {
toast.error("Error fetching source type", {
description: err.message,
});
});
}}
>
Refresh <RefreshCw className="ml-2 h-4 w-4" />
</Button>
</div>
<pre>
<CodeEditor
value={compose || ""}
language="yaml"
readOnly
height="50rem"
/>
</pre>
<pre>
<CodeEditor
value={compose || ""}
language="yaml"
readOnly
height="50rem"
/>
</pre>
</>
)}
</DialogContent>
</Dialog>
);

View File

@@ -80,7 +80,13 @@ export default function Custom404({ statusCode, error }: Props) {
<footer className="mt-auto text-center py-5">
<div className="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8">
<p className="text-sm text-gray-500">
Submit Log in issue on Github
<Link
href="https://github.com/Dokploy/dokploy/issues"
target="_blank"
className="underline hover:text-primary transition-colors"
>
Submit Log in issue on Github
</Link>
</p>
</div>
</footer>

View File

@@ -217,12 +217,12 @@ const Service = (
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
<TabsList
className={cn(
"lg:grid lg:w-fit max-md:overflow-y-scroll justify-start",
"xl:grid xl:w-fit max-md:overflow-y-scroll justify-start",
isCloud && data?.serverId
? "lg:grid-cols-9"
? "xl:grid-cols-9"
: data?.serverId
? "lg:grid-cols-8"
: "lg:grid-cols-9",
? "xl:grid-cols-8"
: "xl:grid-cols-9",
)}
>
<TabsTrigger value="general">General</TabsTrigger>

View File

@@ -3,7 +3,7 @@ import { execAsync } from "../process/execAsync";
import { getS3Credentials, normalizeS3Path } from "./utils";
import { findDestinationById } from "@dokploy/server/services/destination";
import { IS_CLOUD, paths } from "@dokploy/server/constants";
import { mkdtemp } from "node:fs/promises";
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import {
@@ -51,10 +51,20 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
const postgresContainerId = containerId.trim();
const postgresCommand = `docker exec ${postgresContainerId} pg_dump -v -Fc -U dokploy -d dokploy > '${tempDir}/database.sql'`;
// First dump the database inside the container
const dumpCommand = `docker exec ${postgresContainerId} pg_dump -v -Fc -U dokploy -d dokploy -f /tmp/database.sql`;
writeStream.write(`Running dump command: ${dumpCommand}\n`);
await execAsync(dumpCommand);
writeStream.write(`Running command: ${postgresCommand}\n`);
await execAsync(postgresCommand);
// Then copy the file from the container to host
const copyCommand = `docker cp ${postgresContainerId}:/tmp/database.sql ${tempDir}/database.sql`;
writeStream.write(`Copying database dump: ${copyCommand}\n`);
await execAsync(copyCommand);
// Clean up the temp file in the container
const cleanupCommand = `docker exec ${postgresContainerId} rm -f /tmp/database.sql`;
writeStream.write(`Cleaning up temp file: ${cleanupCommand}\n`);
await execAsync(cleanupCommand);
await execAsync(
`rsync -av --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
@@ -77,7 +87,11 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
await updateDeploymentStatus(deployment.deploymentId, "done");
return true;
} finally {
await execAsync(`rm -rf ${tempDir}`);
try {
await rm(tempDir, { recursive: true, force: true });
} catch (cleanupError) {
console.error("Cleanup error:", cleanupError);
}
}
} catch (error) {
console.error("Backup error:", error);