From 4330d7bd99f07b88ccf81d1d4a8a64a13907f710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Vrba?= Date: Mon, 9 Mar 2026 09:25:41 +0100 Subject: [PATCH 1/7] feat(deployments): Add option to copy webhook url by clicking on it --- .../deployments/show-deployments.tsx | 831 +++++++++--------- 1 file changed, 423 insertions(+), 408 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 61841e294..0cc096f5c 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -1,12 +1,12 @@ import { - ChevronDown, - ChevronUp, - Clock, - Loader2, - RefreshCcw, - RocketIcon, - Settings, - Trash2, + ChevronDown, + ChevronUp, + Clock, Copy, + Loader2, + RefreshCcw, + RocketIcon, + Settings, + Trash2, } from "lucide-react"; import React, { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; @@ -17,11 +17,11 @@ import { StatusTooltip } from "@/components/shared/status-tooltip"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { api, type RouterOutputs } from "@/utils/api"; import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings"; @@ -30,441 +30,456 @@ import { ClearDeployments } from "./clear-deployments"; import { KillBuild } from "./kill-build"; import { RefreshToken } from "./refresh-token"; import { ShowDeployment } from "./show-deployment"; +import copy from "copy-to-clipboard"; interface Props { - id: string; - type: - | "application" - | "compose" - | "schedule" - | "server" - | "backup" - | "previewDeployment" - | "volumeBackup"; - refreshToken?: string; - serverId?: string; + id: string; + type: + | "application" + | "compose" + | "schedule" + | "server" + | "backup" + | "previewDeployment" + | "volumeBackup"; + refreshToken?: string; + serverId?: string; } export const formatDuration = (seconds: number) => { - if (seconds < 60) return `${seconds}s`; - const minutes = Math.floor(seconds / 60); - const remainingSeconds = seconds % 60; - return `${minutes}m ${remainingSeconds}s`; + if (seconds < 60) return `${seconds}s`; + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}m ${remainingSeconds}s`; }; export const ShowDeployments = ({ - id, - type, - refreshToken, - serverId, + id, + type, + refreshToken, + serverId, }: Props) => { - const [activeLog, setActiveLog] = useState< - RouterOutputs["deployment"]["all"][number] | null - >(null); - const { data: deployments, isPending: isLoadingDeployments } = - api.deployment.allByType.useQuery( - { - id, - type, - }, - { - enabled: !!id, - refetchInterval: 1000, - }, - ); + const [activeLog, setActiveLog] = useState< + RouterOutputs["deployment"]["all"][number] | null + >(null); + const { data: deployments, isPending: isLoadingDeployments } = + api.deployment.allByType.useQuery( + { + id, + type, + }, + { + enabled: !!id, + refetchInterval: 1000, + }, + ); - const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: isCloud } = api.settings.isCloud.useQuery(); - const { mutateAsync: rollback, isPending: isRollingBack } = - api.rollback.rollback.useMutation(); - const { mutateAsync: killProcess, isPending: isKillingProcess } = - api.deployment.killProcess.useMutation(); - const { mutateAsync: removeDeployment, isPending: isRemovingDeployment } = - api.deployment.removeDeployment.useMutation(); + const { mutateAsync: rollback, isPending: isRollingBack } = + api.rollback.rollback.useMutation(); + const { mutateAsync: killProcess, isPending: isKillingProcess } = + api.deployment.killProcess.useMutation(); + const { mutateAsync: removeDeployment, isPending: isRemovingDeployment } = + api.deployment.removeDeployment.useMutation(); - // Cancel deployment mutations - const { - mutateAsync: cancelApplicationDeployment, - isPending: isCancellingApp, - } = api.application.cancelDeployment.useMutation(); - const { - mutateAsync: cancelComposeDeployment, - isPending: isCancellingCompose, - } = api.compose.cancelDeployment.useMutation(); + // Cancel deployment mutations + const { + mutateAsync: cancelApplicationDeployment, + isPending: isCancellingApp, + } = api.application.cancelDeployment.useMutation(); + const { + mutateAsync: cancelComposeDeployment, + isPending: isCancellingCompose, + } = api.compose.cancelDeployment.useMutation(); - const [url, setUrl] = React.useState(""); - const [expandedDescriptions, setExpandedDescriptions] = useState>( - new Set(), - ); + const [url, setUrl] = React.useState(""); + const [expandedDescriptions, setExpandedDescriptions] = useState>( + new Set(), + ); - const MAX_DESCRIPTION_LENGTH = 200; + const MAX_DESCRIPTION_LENGTH = 200; - const truncateDescription = (description: string): string => { - if (description.length <= MAX_DESCRIPTION_LENGTH) { - return description; - } - const truncated = description.slice(0, MAX_DESCRIPTION_LENGTH); - const lastSpace = truncated.lastIndexOf(" "); - if (lastSpace > MAX_DESCRIPTION_LENGTH - 20 && lastSpace > 0) { - return `${truncated.slice(0, lastSpace)}...`; - } - return `${truncated}...`; - }; + const truncateDescription = (description: string): string => { + if (description.length <= MAX_DESCRIPTION_LENGTH) { + return description; + } + const truncated = description.slice(0, MAX_DESCRIPTION_LENGTH); + const lastSpace = truncated.lastIndexOf(" "); + if (lastSpace > MAX_DESCRIPTION_LENGTH - 20 && lastSpace > 0) { + return `${truncated.slice(0, lastSpace)}...`; + } + return `${truncated}...`; + }; - // Check for stuck deployment (more than 9 minutes) - only for the most recent deployment - const stuckDeployment = useMemo(() => { - if (!isCloud || !deployments || deployments.length === 0) return null; + // Check for stuck deployment (more than 9 minutes) - only for the most recent deployment + const stuckDeployment = useMemo(() => { + if (!isCloud || !deployments || deployments.length === 0) return null; - const now = Date.now(); - const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds + const now = Date.now(); + const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds - // Get the most recent deployment (first in the list since they're sorted by date) - const mostRecentDeployment = deployments[0]; + // Get the most recent deployment (first in the list since they're sorted by date) + const mostRecentDeployment = deployments[0]; - if ( - !mostRecentDeployment || - mostRecentDeployment.status !== "running" || - !mostRecentDeployment.startedAt - ) { - return null; - } + if ( + !mostRecentDeployment || + mostRecentDeployment.status !== "running" || + !mostRecentDeployment.startedAt + ) { + return null; + } - const startTime = new Date(mostRecentDeployment.startedAt).getTime(); - const elapsed = now - startTime; + const startTime = new Date(mostRecentDeployment.startedAt).getTime(); + const elapsed = now - startTime; - return elapsed > NINE_MINUTES ? mostRecentDeployment : null; - }, [isCloud, deployments]); - useEffect(() => { - setUrl(document.location.origin); - }, []); + return elapsed > NINE_MINUTES ? mostRecentDeployment : null; + }, [isCloud, deployments]); + useEffect(() => { + setUrl(document.location.origin); + }, []); - return ( - - -
- Deployments - - See the last 10 deployments for this {type} - -
-
- {(type === "application" || type === "compose") && ( - - )} - {(type === "application" || type === "compose") && ( - - )} - {(type === "application" || type === "compose") && ( - - )} - {type === "application" && ( - - - - )} -
-
- - {stuckDeployment && (type === "application" || type === "compose") && ( - -
-
-
- Build appears to be stuck -
-

- Hey! Looks like the build has been running for more than 10 - minutes. Would you like to cancel this deployment? -

-
- -
-
- )} - {refreshToken && ( -
+ return ( + + +
+ Deployments + + See the last 10 deployments for this {type} + +
+
+ {(type === "application" || type === "compose") && ( + + )} + {(type === "application" || type === "compose") && ( + + )} + {(type === "application" || type === "compose") && ( + + )} + {type === "application" && ( + + + + )} +
+
+ + {stuckDeployment && (type === "application" || type === "compose") && ( + +
+
+
+ Build appears to be stuck +
+

+ Hey! Looks like the build has been running for more than 10 + minutes. Would you like to cancel this deployment? +

+
+ +
+
+ )} + {refreshToken && ( +
If you want to re-deploy this application use this URL in the config of your git provider or docker -
- Webhook URL: -
- - {`${url}/api/deploy${ - type === "compose" ? "/compose" : "" - }/${refreshToken}`} - - {(type === "application" || type === "compose") && ( - - )} -
-
-
- )} +
+ Webhook URL: +
+ { + copy(`${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`); + toast.success("Copied to clipboard."); + }} + > + {`${url}/api/deploy${ + type === "compose" ? "/compose" : "" + }/${refreshToken}`} + + + {(type === "application" || type === "compose") && ( + + )} +
+
+
+ )} - {isLoadingDeployments ? ( -
- - + {isLoadingDeployments ? ( +
+ + Loading deployments... -
- ) : deployments?.length === 0 ? ( -
- - +
+ ) : deployments?.length === 0 ? ( +
+ + No deployments found -
- ) : ( -
- {deployments?.map((deployment, index) => { - const titleText = deployment?.title?.trim() || ""; - const needsTruncation = titleText.length > MAX_DESCRIPTION_LENGTH; - const isExpanded = expandedDescriptions.has( - deployment.deploymentId, - ); - const canDelete = - deployment.status === "done" || deployment.status === "error"; +
+ ) : ( +
+ {deployments?.map((deployment, index) => { + const titleText = deployment?.title?.trim() || ""; + const needsTruncation = titleText.length > MAX_DESCRIPTION_LENGTH; + const isExpanded = expandedDescriptions.has( + deployment.deploymentId, + ); + const canDelete = + deployment.status === "done" || deployment.status === "error"; - return ( -
-
- + return ( +
+
+ {index + 1}. {deployment.status} - + -
- +
+ {isExpanded || !needsTruncation - ? titleText - : truncateDescription(titleText)} + ? titleText + : truncateDescription(titleText)} - {needsTruncation && ( - - )} - {/* Hash (from description) - shown in compact form */} - {deployment.description?.trim() && ( - + {needsTruncation && ( + + )} + {/* Hash (from description) - shown in compact form */} + {deployment.description?.trim() && ( + {deployment.description} - )} -
-
-
-
- - {deployment.startedAt && deployment.finishedAt && ( - - - {formatDuration( - Math.floor( - (new Date(deployment.finishedAt).getTime() - - new Date(deployment.startedAt).getTime()) / - 1000, - ), - )} - - )} -
+ )} +
+
+
+
+ + {deployment.startedAt && deployment.finishedAt && ( + + + {formatDuration( + Math.floor( + (new Date(deployment.finishedAt).getTime() - + new Date(deployment.startedAt).getTime()) / + 1000, + ), + )} + + )} +
-
- {deployment.pid && deployment.status === "running" && ( - { - await killProcess({ - deploymentId: deployment.deploymentId, - }) - .then(() => { - toast.success("Process killed successfully"); - }) - .catch(() => { - toast.error("Error killing process"); - }); - }} - > - - - )} - +
+ {deployment.pid && deployment.status === "running" && ( + { + await killProcess({ + deploymentId: deployment.deploymentId, + }) + .then(() => { + toast.success("Process killed successfully"); + }) + .catch(() => { + toast.error("Error killing process"); + }); + }} + > + + + )} + - {canDelete && ( - { - try { - await removeDeployment({ - deploymentId: deployment.deploymentId, - }); - toast.success("Deployment deleted successfully"); - } catch (error) { - toast.error("Error deleting deployment"); - } - }} - > - - - )} + {canDelete && ( + { + try { + await removeDeployment({ + deploymentId: deployment.deploymentId, + }); + toast.success("Deployment deleted successfully"); + } catch (error) { + toast.error("Error deleting deployment"); + } + }} + > + + + )} - {deployment?.rollback && - deployment.status === "done" && - type === "application" && ( - -

- Are you sure you want to rollback to this - deployment? -

- - Please wait a few seconds while the image is - pulled from the registry. Your application - should be running shortly. - -
- } - type="default" - onClick={async () => { - await rollback({ - rollbackId: deployment.rollback.rollbackId, - }) - .then(() => { - toast.success( - "Rollback initiated successfully", - ); - }) - .catch(() => { - toast.error("Error initiating rollback"); - }); - }} - > - - - )} -
-
-
- ); - })} -
- )} - setActiveLog(null)} - logPath={activeLog?.logPath || ""} - errorMessage={activeLog?.errorMessage || ""} - /> - - - ); + {deployment?.rollback && + deployment.status === "done" && + type === "application" && ( + +

+ Are you sure you want to rollback to this + deployment? +

+ + Please wait a few seconds while the image is + pulled from the registry. Your application + should be running shortly. + +
+ } + type="default" + onClick={async () => { + await rollback({ + rollbackId: deployment.rollback.rollbackId, + }) + .then(() => { + toast.success( + "Rollback initiated successfully", + ); + }) + .catch(() => { + toast.error("Error initiating rollback"); + }); + }} + > + + + )} +
+
+ + ); + })} + + )} + setActiveLog(null)} + logPath={activeLog?.logPath || ""} + errorMessage={activeLog?.errorMessage || ""} + /> +
+
+ ); }; From d8c7c1eaf44779b37bca3452b19c0dfed352ea9d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 08:28:35 +0000 Subject: [PATCH 2/7] [autofix.ci] apply automated fixes --- .../deployments/show-deployments.tsx | 841 +++++++++--------- 1 file changed, 419 insertions(+), 422 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 0cc096f5c..fe17697ff 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -1,12 +1,13 @@ import { - ChevronDown, - ChevronUp, - Clock, Copy, - Loader2, - RefreshCcw, - RocketIcon, - Settings, - Trash2, + ChevronDown, + ChevronUp, + Clock, + Copy, + Loader2, + RefreshCcw, + RocketIcon, + Settings, + Trash2, } from "lucide-react"; import React, { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; @@ -17,11 +18,11 @@ import { StatusTooltip } from "@/components/shared/status-tooltip"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { api, type RouterOutputs } from "@/utils/api"; import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings"; @@ -33,453 +34,449 @@ import { ShowDeployment } from "./show-deployment"; import copy from "copy-to-clipboard"; interface Props { - id: string; - type: - | "application" - | "compose" - | "schedule" - | "server" - | "backup" - | "previewDeployment" - | "volumeBackup"; - refreshToken?: string; - serverId?: string; + id: string; + type: + | "application" + | "compose" + | "schedule" + | "server" + | "backup" + | "previewDeployment" + | "volumeBackup"; + refreshToken?: string; + serverId?: string; } export const formatDuration = (seconds: number) => { - if (seconds < 60) return `${seconds}s`; - const minutes = Math.floor(seconds / 60); - const remainingSeconds = seconds % 60; - return `${minutes}m ${remainingSeconds}s`; + if (seconds < 60) return `${seconds}s`; + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}m ${remainingSeconds}s`; }; export const ShowDeployments = ({ - id, - type, - refreshToken, - serverId, + id, + type, + refreshToken, + serverId, }: Props) => { - const [activeLog, setActiveLog] = useState< - RouterOutputs["deployment"]["all"][number] | null - >(null); - const { data: deployments, isPending: isLoadingDeployments } = - api.deployment.allByType.useQuery( - { - id, - type, - }, - { - enabled: !!id, - refetchInterval: 1000, - }, - ); + const [activeLog, setActiveLog] = useState< + RouterOutputs["deployment"]["all"][number] | null + >(null); + const { data: deployments, isPending: isLoadingDeployments } = + api.deployment.allByType.useQuery( + { + id, + type, + }, + { + enabled: !!id, + refetchInterval: 1000, + }, + ); - const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: isCloud } = api.settings.isCloud.useQuery(); - const { mutateAsync: rollback, isPending: isRollingBack } = - api.rollback.rollback.useMutation(); - const { mutateAsync: killProcess, isPending: isKillingProcess } = - api.deployment.killProcess.useMutation(); - const { mutateAsync: removeDeployment, isPending: isRemovingDeployment } = - api.deployment.removeDeployment.useMutation(); + const { mutateAsync: rollback, isPending: isRollingBack } = + api.rollback.rollback.useMutation(); + const { mutateAsync: killProcess, isPending: isKillingProcess } = + api.deployment.killProcess.useMutation(); + const { mutateAsync: removeDeployment, isPending: isRemovingDeployment } = + api.deployment.removeDeployment.useMutation(); - // Cancel deployment mutations - const { - mutateAsync: cancelApplicationDeployment, - isPending: isCancellingApp, - } = api.application.cancelDeployment.useMutation(); - const { - mutateAsync: cancelComposeDeployment, - isPending: isCancellingCompose, - } = api.compose.cancelDeployment.useMutation(); + // Cancel deployment mutations + const { + mutateAsync: cancelApplicationDeployment, + isPending: isCancellingApp, + } = api.application.cancelDeployment.useMutation(); + const { + mutateAsync: cancelComposeDeployment, + isPending: isCancellingCompose, + } = api.compose.cancelDeployment.useMutation(); - const [url, setUrl] = React.useState(""); - const [expandedDescriptions, setExpandedDescriptions] = useState>( - new Set(), - ); + const [url, setUrl] = React.useState(""); + const [expandedDescriptions, setExpandedDescriptions] = useState>( + new Set(), + ); - const MAX_DESCRIPTION_LENGTH = 200; + const MAX_DESCRIPTION_LENGTH = 200; - const truncateDescription = (description: string): string => { - if (description.length <= MAX_DESCRIPTION_LENGTH) { - return description; - } - const truncated = description.slice(0, MAX_DESCRIPTION_LENGTH); - const lastSpace = truncated.lastIndexOf(" "); - if (lastSpace > MAX_DESCRIPTION_LENGTH - 20 && lastSpace > 0) { - return `${truncated.slice(0, lastSpace)}...`; - } - return `${truncated}...`; - }; + const truncateDescription = (description: string): string => { + if (description.length <= MAX_DESCRIPTION_LENGTH) { + return description; + } + const truncated = description.slice(0, MAX_DESCRIPTION_LENGTH); + const lastSpace = truncated.lastIndexOf(" "); + if (lastSpace > MAX_DESCRIPTION_LENGTH - 20 && lastSpace > 0) { + return `${truncated.slice(0, lastSpace)}...`; + } + return `${truncated}...`; + }; - // Check for stuck deployment (more than 9 minutes) - only for the most recent deployment - const stuckDeployment = useMemo(() => { - if (!isCloud || !deployments || deployments.length === 0) return null; + // Check for stuck deployment (more than 9 minutes) - only for the most recent deployment + const stuckDeployment = useMemo(() => { + if (!isCloud || !deployments || deployments.length === 0) return null; - const now = Date.now(); - const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds + const now = Date.now(); + const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds - // Get the most recent deployment (first in the list since they're sorted by date) - const mostRecentDeployment = deployments[0]; + // Get the most recent deployment (first in the list since they're sorted by date) + const mostRecentDeployment = deployments[0]; - if ( - !mostRecentDeployment || - mostRecentDeployment.status !== "running" || - !mostRecentDeployment.startedAt - ) { - return null; - } + if ( + !mostRecentDeployment || + mostRecentDeployment.status !== "running" || + !mostRecentDeployment.startedAt + ) { + return null; + } - const startTime = new Date(mostRecentDeployment.startedAt).getTime(); - const elapsed = now - startTime; + const startTime = new Date(mostRecentDeployment.startedAt).getTime(); + const elapsed = now - startTime; - return elapsed > NINE_MINUTES ? mostRecentDeployment : null; - }, [isCloud, deployments]); - useEffect(() => { - setUrl(document.location.origin); - }, []); + return elapsed > NINE_MINUTES ? mostRecentDeployment : null; + }, [isCloud, deployments]); + useEffect(() => { + setUrl(document.location.origin); + }, []); - return ( - - -
- Deployments - - See the last 10 deployments for this {type} - -
-
- {(type === "application" || type === "compose") && ( - - )} - {(type === "application" || type === "compose") && ( - - )} - {(type === "application" || type === "compose") && ( - - )} - {type === "application" && ( - - - - )} -
-
- - {stuckDeployment && (type === "application" || type === "compose") && ( - -
-
-
- Build appears to be stuck -
-

- Hey! Looks like the build has been running for more than 10 - minutes. Would you like to cancel this deployment? -

-
- -
-
- )} - {refreshToken && ( -
+ return ( + + +
+ Deployments + + See the last 10 deployments for this {type} + +
+
+ {(type === "application" || type === "compose") && ( + + )} + {(type === "application" || type === "compose") && ( + + )} + {(type === "application" || type === "compose") && ( + + )} + {type === "application" && ( + + + + )} +
+
+ + {stuckDeployment && (type === "application" || type === "compose") && ( + +
+
+
+ Build appears to be stuck +
+

+ Hey! Looks like the build has been running for more than 10 + minutes. Would you like to cancel this deployment? +

+
+ +
+
+ )} + {refreshToken && ( +
If you want to re-deploy this application use this URL in the config of your git provider or docker -
- Webhook URL: -
- { - copy(`${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`); - toast.success("Copied to clipboard."); - }} - > - {`${url}/api/deploy${ - type === "compose" ? "/compose" : "" - }/${refreshToken}`} - - - {(type === "application" || type === "compose") && ( - - )} -
-
-
- )} +
+ Webhook URL: +
+ { + copy( + `${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`, + ); + toast.success("Copied to clipboard."); + }} + > + {`${url}/api/deploy${ + type === "compose" ? "/compose" : "" + }/${refreshToken}`} + + + {(type === "application" || type === "compose") && ( + + )} +
+
+
+ )} - {isLoadingDeployments ? ( -
- - + {isLoadingDeployments ? ( +
+ + Loading deployments... -
- ) : deployments?.length === 0 ? ( -
- - +
+ ) : deployments?.length === 0 ? ( +
+ + No deployments found -
- ) : ( -
- {deployments?.map((deployment, index) => { - const titleText = deployment?.title?.trim() || ""; - const needsTruncation = titleText.length > MAX_DESCRIPTION_LENGTH; - const isExpanded = expandedDescriptions.has( - deployment.deploymentId, - ); - const canDelete = - deployment.status === "done" || deployment.status === "error"; +
+ ) : ( +
+ {deployments?.map((deployment, index) => { + const titleText = deployment?.title?.trim() || ""; + const needsTruncation = titleText.length > MAX_DESCRIPTION_LENGTH; + const isExpanded = expandedDescriptions.has( + deployment.deploymentId, + ); + const canDelete = + deployment.status === "done" || deployment.status === "error"; - return ( -
-
- + return ( +
+
+ {index + 1}. {deployment.status} - + -
- +
+ {isExpanded || !needsTruncation - ? titleText - : truncateDescription(titleText)} + ? titleText + : truncateDescription(titleText)} - {needsTruncation && ( - - )} - {/* Hash (from description) - shown in compact form */} - {deployment.description?.trim() && ( - + {needsTruncation && ( + + )} + {/* Hash (from description) - shown in compact form */} + {deployment.description?.trim() && ( + {deployment.description} - )} -
-
-
-
- - {deployment.startedAt && deployment.finishedAt && ( - - - {formatDuration( - Math.floor( - (new Date(deployment.finishedAt).getTime() - - new Date(deployment.startedAt).getTime()) / - 1000, - ), - )} - - )} -
+ )} +
+
+
+
+ + {deployment.startedAt && deployment.finishedAt && ( + + + {formatDuration( + Math.floor( + (new Date(deployment.finishedAt).getTime() - + new Date(deployment.startedAt).getTime()) / + 1000, + ), + )} + + )} +
-
- {deployment.pid && deployment.status === "running" && ( - { - await killProcess({ - deploymentId: deployment.deploymentId, - }) - .then(() => { - toast.success("Process killed successfully"); - }) - .catch(() => { - toast.error("Error killing process"); - }); - }} - > - - - )} - +
+ {deployment.pid && deployment.status === "running" && ( + { + await killProcess({ + deploymentId: deployment.deploymentId, + }) + .then(() => { + toast.success("Process killed successfully"); + }) + .catch(() => { + toast.error("Error killing process"); + }); + }} + > + + + )} + - {canDelete && ( - { - try { - await removeDeployment({ - deploymentId: deployment.deploymentId, - }); - toast.success("Deployment deleted successfully"); - } catch (error) { - toast.error("Error deleting deployment"); - } - }} - > - - - )} + {canDelete && ( + { + try { + await removeDeployment({ + deploymentId: deployment.deploymentId, + }); + toast.success("Deployment deleted successfully"); + } catch (error) { + toast.error("Error deleting deployment"); + } + }} + > + + + )} - {deployment?.rollback && - deployment.status === "done" && - type === "application" && ( - -

- Are you sure you want to rollback to this - deployment? -

- - Please wait a few seconds while the image is - pulled from the registry. Your application - should be running shortly. - -
- } - type="default" - onClick={async () => { - await rollback({ - rollbackId: deployment.rollback.rollbackId, - }) - .then(() => { - toast.success( - "Rollback initiated successfully", - ); - }) - .catch(() => { - toast.error("Error initiating rollback"); - }); - }} - > - - - )} -
-
-
- ); - })} -
- )} - setActiveLog(null)} - logPath={activeLog?.logPath || ""} - errorMessage={activeLog?.errorMessage || ""} - /> - - - ); + {deployment?.rollback && + deployment.status === "done" && + type === "application" && ( + +

+ Are you sure you want to rollback to this + deployment? +

+ + Please wait a few seconds while the image is + pulled from the registry. Your application + should be running shortly. + +
+ } + type="default" + onClick={async () => { + await rollback({ + rollbackId: deployment.rollback.rollbackId, + }) + .then(() => { + toast.success( + "Rollback initiated successfully", + ); + }) + .catch(() => { + toast.error("Error initiating rollback"); + }); + }} + > + + + )} +
+
+ + ); + })} + + )} + setActiveLog(null)} + logPath={activeLog?.logPath || ""} + errorMessage={activeLog?.errorMessage || ""} + /> +
+
+ ); }; From f1d4543d5e4d0dedd4290fa7c45bf55e257f792b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Vrba?= Date: Mon, 9 Mar 2026 09:33:30 +0100 Subject: [PATCH 3/7] Code review fixes --- .../dashboard/application/deployments/show-deployments.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index fe17697ff..67f1c0e87 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -227,7 +227,7 @@ export const ShowDeployments = ({ Webhook URL:
{ copy( From b9ca6ea9dbf89fab0d607cb7a4a2a2f0c6541f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Vrba?= Date: Mon, 9 Mar 2026 09:38:00 +0100 Subject: [PATCH 4/7] Code review fixes --- .../dashboard/application/deployments/show-deployments.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 67f1c0e87..f82c9589b 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -227,6 +227,8 @@ export const ShowDeployments = ({ Webhook URL:
{ From 3e4a1b92ebbc69e0b999e890fe511fb3a83bdcd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Vrba?= Date: Mon, 9 Mar 2026 09:48:37 +0100 Subject: [PATCH 5/7] Code review fixes --- .../deployments/show-deployments.tsx | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index f82c9589b..6de8c1cb0 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -11,6 +11,7 @@ import { } from "lucide-react"; import React, { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; +import copy from "copy-to-clipboard"; import { AlertBlock } from "@/components/shared/alert-block"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { DialogAction } from "@/components/shared/dialog-action"; @@ -31,7 +32,6 @@ import { ClearDeployments } from "./clear-deployments"; import { KillBuild } from "./kill-build"; import { RefreshToken } from "./refresh-token"; import { ShowDeployment } from "./show-deployment"; -import copy from "copy-to-clipboard"; interface Props { id: string; @@ -99,6 +99,11 @@ export const ShowDeployments = ({ new Set(), ); + const webhookUrl = useMemo( + () => `${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`, + [url, refreshToken, type] + ); + const MAX_DESCRIPTION_LENGTH = 200; const truncateDescription = (description: string): string => { @@ -231,17 +236,20 @@ export const ShowDeployments = ({ tabIndex={0} className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer whitespace-normal break-all" variant="outline" + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + copy(webhookUrl); + toast.success("Copied to clipboard."); + } + }} onClick={() => { - copy( - `${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`, - ); + copy(webhookUrl); toast.success("Copied to clipboard."); }} > - {`${url}/api/deploy${ - type === "compose" ? "/compose" : "" - }/${refreshToken}`} - + {webhookUrl} + {(type === "application" || type === "compose") && ( From 6866e2b63abc2bbeaf68231a1348c34b386666cc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 08:49:06 +0000 Subject: [PATCH 6/7] [autofix.ci] apply automated fixes --- .../dashboard/application/deployments/show-deployments.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 6de8c1cb0..c50cd1607 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -100,8 +100,9 @@ export const ShowDeployments = ({ ); const webhookUrl = useMemo( - () => `${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`, - [url, refreshToken, type] + () => + `${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`, + [url, refreshToken, type], ); const MAX_DESCRIPTION_LENGTH = 200; From de201d0b0af2056a995fc3c3b0fb4ac814a336b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Vrba?= Date: Mon, 9 Mar 2026 09:59:36 +0100 Subject: [PATCH 7/7] Add aria-label to webhook URL badge --- .../dashboard/application/deployments/show-deployments.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index c50cd1607..3cecef1ec 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -235,6 +235,7 @@ export const ShowDeployments = ({ {