diff --git a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx index 5fe7ffb07..eb85f383b 100644 --- a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx @@ -15,11 +15,15 @@ import { Paintbrush } from "lucide-react"; import { toast } from "sonner"; interface Props { - applicationId: string; + id: string; + type: "application" | "compose"; } -export const CancelQueues = ({ applicationId }: Props) => { - const { mutateAsync, isLoading } = api.application.cleanQueues.useMutation(); +export const CancelQueues = ({ id, type }: Props) => { + const { mutateAsync, isLoading } = + type === "application" + ? api.application.cleanQueues.useMutation() + : api.compose.cleanQueues.useMutation(); const { data: isCloud } = api.settings.isCloud.useQuery(); if (isCloud) { @@ -48,7 +52,8 @@ export const CancelQueues = ({ applicationId }: Props) => { { await mutateAsync({ - applicationId, + applicationId: id || "", + composeId: id || "", }) .then(() => { toast.success("Queues are being cleaned"); diff --git a/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx b/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx index b80450f9f..abfe37c3c 100644 --- a/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx @@ -14,10 +14,14 @@ import { RefreshCcw } from "lucide-react"; import { toast } from "sonner"; interface Props { - applicationId: string; + id: string; + type: "application" | "compose"; } -export const RefreshToken = ({ applicationId }: Props) => { - const { mutateAsync } = api.application.refreshToken.useMutation(); +export const RefreshToken = ({ id, type }: Props) => { + const { mutateAsync } = + type === "application" + ? api.application.refreshToken.useMutation() + : api.compose.refreshToken.useMutation(); const utils = api.useUtils(); return ( @@ -37,12 +41,19 @@ export const RefreshToken = ({ applicationId }: Props) => { { await mutateAsync({ - applicationId, + applicationId: id || "", + composeId: id || "", }) .then(() => { - utils.application.one.invalidate({ - applicationId, - }); + if (type === "application") { + utils.application.one.invalidate({ + applicationId: id, + }); + } else { + utils.compose.one.invalidate({ + composeId: id, + }); + } toast.success("Refresh updated"); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 3a46835d7..dc8d3065f 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -9,7 +9,7 @@ import { CardTitle, } from "@/components/ui/card"; import { type RouterOutputs, api } from "@/utils/api"; -import { RocketIcon, Clock } from "lucide-react"; +import { RocketIcon, Clock, Loader2 } from "lucide-react"; import React, { useEffect, useState } from "react"; import { CancelQueues } from "./cancel-queues"; import { RefreshToken } from "./refresh-token"; @@ -17,21 +17,34 @@ import { ShowDeployment } from "./show-deployment"; import { Badge } from "@/components/ui/badge"; import { formatDuration } from "../schedules/show-schedules-logs"; interface Props { - applicationId: string; + id: string; + type: "application" | "compose"; } -export const ShowDeployments = ({ applicationId }: Props) => { +export const ShowDeployments = ({ id, type }: Props) => { const [activeLog, setActiveLog] = useState< RouterOutputs["deployment"]["all"][number] | null >(null); - const { data } = api.application.one.useQuery({ applicationId }); - const { data: deployments } = api.deployment.all.useQuery( - { applicationId }, - { - enabled: !!applicationId, - refetchInterval: 1000, - }, - ); + const { data } = + type === "application" + ? api.application.one.useQuery({ applicationId: id }) + : api.compose.one.useQuery({ composeId: id }); + const { data: deployments, isLoading: isLoadingDeployments } = + type === "application" + ? api.deployment.all.useQuery( + { applicationId: id }, + { + enabled: !!id, + refetchInterval: 1000, + }, + ) + : api.deployment.allByCompose.useQuery( + { composeId: id }, + { + enabled: !!id, + refetchInterval: 1000, + }, + ); const [url, setUrl] = React.useState(""); useEffect(() => { @@ -44,10 +57,10 @@ export const ShowDeployments = ({ applicationId }: Props) => {
Deployments - See all the 10 last deployments for this application + See all the 10 last deployments for this {type}
- +
@@ -61,12 +74,19 @@ export const ShowDeployments = ({ applicationId }: Props) => { {`${url}/api/deploy/${data?.refreshToken}`} - +
- {data?.deployments?.length === 0 ? ( -
+ {isLoadingDeployments ? ( +
+ + + Loading deployments... + +
+ ) : data?.deployments?.length === 0 ? ( +
No deployments found diff --git a/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx deleted file mode 100644 index a430ae18f..000000000 --- a/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Paintbrush } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} - -export const CancelQueuesCompose = ({ composeId }: Props) => { - const { mutateAsync, isLoading } = api.compose.cleanQueues.useMutation(); - const { data: isCloud } = api.settings.isCloud.useQuery(); - - if (isCloud) { - return null; - } - return ( - - - - - - - - Are you sure to cancel the incoming deployments? - - - This will cancel all the incoming deployments - - - - Cancel - { - await mutateAsync({ - composeId, - }) - .then(() => { - toast.success("Queues are being cleaned"); - }) - .catch((err) => { - toast.error(err.message); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx deleted file mode 100644 index b062b0994..000000000 --- a/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} -export const RefreshTokenCompose = ({ composeId }: Props) => { - const { mutateAsync } = api.compose.refreshToken.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently change the token - and all the previous tokens will be invalidated - - - - Cancel - { - await mutateAsync({ - composeId, - }) - .then(() => { - utils.compose.one.invalidate({ - composeId, - }); - toast.success("Refresh Token updated"); - }) - .catch(() => { - toast.error("Error updating the refresh token"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx deleted file mode 100644 index 7c191a14c..000000000 --- a/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { Checkbox } from "@/components/ui/checkbox"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Loader2 } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; -import { TerminalLine } from "../../docker/logs/terminal-line"; -import { type LogLine, parseLogs } from "../../docker/logs/utils"; - -interface Props { - logPath: string | null; - serverId?: string; - open: boolean; - onClose: () => void; - errorMessage?: string; -} -export const ShowDeploymentCompose = ({ - logPath, - open, - onClose, - serverId, - errorMessage, -}: Props) => { - const [data, setData] = useState(""); - const [filteredLogs, setFilteredLogs] = useState([]); - const [showExtraLogs, setShowExtraLogs] = useState(false); - const wsRef = useRef(null); // Ref to hold WebSocket instance - const [autoScroll, setAutoScroll] = useState(true); - const scrollRef = useRef(null); - - const scrollToBottom = () => { - if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }; - - const handleScroll = () => { - if (!scrollRef.current) return; - - const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; - const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; - setAutoScroll(isAtBottom); - }; - - useEffect(() => { - if (!open || !logPath) return; - - setData(""); - const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - - const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}&serverId=${serverId}`; - const ws = new WebSocket(wsUrl); - - wsRef.current = ws; // Store WebSocket instance in ref - - ws.onmessage = (e) => { - setData((currentData) => currentData + e.data); - }; - - ws.onerror = (error) => { - console.error("WebSocket error: ", error); - }; - - ws.onclose = () => { - wsRef.current = null; - }; - - return () => { - if (wsRef.current?.readyState === WebSocket.OPEN) { - ws.close(); - wsRef.current = null; - } - }; - }, [logPath, open]); - - useEffect(() => { - const logs = parseLogs(data); - let filteredLogsResult = logs; - if (serverId) { - let hideSubsequentLogs = false; - filteredLogsResult = logs.filter((log) => { - if ( - log.message.includes( - "===================================EXTRA LOGS============================================", - ) - ) { - hideSubsequentLogs = true; - return showExtraLogs; - } - return showExtraLogs ? true : !hideSubsequentLogs; - }); - } - - setFilteredLogs(filteredLogsResult); - }, [data, showExtraLogs]); - - useEffect(() => { - scrollToBottom(); - - if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }, [filteredLogs, autoScroll]); - - const optionalErrors = parseLogs(errorMessage || ""); - - return ( - { - onClose(); - if (!e) { - setData(""); - } - - if (wsRef.current) { - if (wsRef.current.readyState === WebSocket.OPEN) { - wsRef.current.close(); - } - } - }} - > - - - Deployment - - - See all the details of this deployment |{" "} - - {filteredLogs.length} lines - - - {serverId && ( -
- - setShowExtraLogs(checked as boolean) - } - /> - -
- )} -
-
- -
- {filteredLogs.length > 0 ? ( - filteredLogs.map((log: LogLine, index: number) => ( - - )) - ) : ( - <> - {optionalErrors.length > 0 ? ( - optionalErrors.map((log: LogLine, index: number) => ( - - )) - ) : ( -
- -
- )} - - )} -
-
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx deleted file mode 100644 index d79d41723..000000000 --- a/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { DateTooltip } from "@/components/shared/date-tooltip"; -import { StatusTooltip } from "@/components/shared/status-tooltip"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { type RouterOutputs, api } from "@/utils/api"; -import { RocketIcon, Clock } from "lucide-react"; -import React, { useEffect, useState } from "react"; -import { CancelQueuesCompose } from "./cancel-queues-compose"; -import { RefreshTokenCompose } from "./refresh-token-compose"; -import { ShowDeploymentCompose } from "./show-deployment-compose"; -import { Badge } from "@/components/ui/badge"; -import { formatDuration } from "@/components/dashboard/application/schedules/show-schedules-logs"; -interface Props { - composeId: string; -} -export const ShowDeploymentsCompose = ({ composeId }: Props) => { - const [activeLog, setActiveLog] = useState< - RouterOutputs["deployment"]["all"][number] | null - >(null); - const { data } = api.compose.one.useQuery({ composeId }); - const { data: deployments } = api.deployment.allByCompose.useQuery( - { composeId }, - { - enabled: !!composeId, - refetchInterval: 5000, - }, - ); - const [url, setUrl] = React.useState(""); - useEffect(() => { - setUrl(document.location.origin); - }, []); - - return ( - - -
- Deployments - - See all the 10 last deployments for this compose - -
- - {/* */} -
- -
- - 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/compose/${data?.refreshToken}`} - - -
-
-
- {data?.deployments?.length === 0 ? ( -
- - - No deployments found - -
- ) : ( -
- {deployments?.map((deployment) => ( -
-
- - {deployment.status} - - - - - {deployment.title} - - {deployment.description && ( - - {deployment.description} - - )} -
-
-
- - {deployment.startedAt && deployment.finishedAt && ( - - - {formatDuration( - Math.floor( - (new Date(deployment.finishedAt).getTime() - - new Date(deployment.startedAt).getTime()) / - 1000, - ), - )} - - )} -
- - -
-
- ))} -
- )} - setActiveLog(null)} - logPath={activeLog?.logPath || ""} - errorMessage={activeLog?.errorMessage || ""} - /> -
-
- ); -}; diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx index 3971d040b..61f9178cd 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx @@ -320,7 +320,7 @@ const Service = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx index 5875c47e5..befc7ff6c 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx @@ -1,5 +1,6 @@ import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import"; import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-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 { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules"; @@ -333,7 +334,7 @@ const Service = (
- +