Skip to content

Commit e60c68d

Browse files
authored
Merge pull request #2989 from Harikrishnan1367709/Better-deployment-logs-for-long-commit-message-#2973
feat: Add expandable commit messages for deployment logs
2 parents d854979 + f46444e commit e60c68d

File tree

1 file changed

+162
-91
lines changed

1 file changed

+162
-91
lines changed

apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx

Lines changed: 162 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
1+
import {
2+
ChevronDown,
3+
ChevronUp,
4+
Clock,
5+
Loader2,
6+
RefreshCcw,
7+
RocketIcon,
8+
Settings,
9+
} from "lucide-react";
210
import React, { useEffect, useMemo, useState } from "react";
311
import { toast } from "sonner";
412
import { AlertBlock } from "@/components/shared/alert-block";
@@ -80,6 +88,23 @@ export const ShowDeployments = ({
8088
} = api.compose.cancelDeployment.useMutation();
8189

8290
const [url, setUrl] = React.useState("");
91+
const [expandedDescriptions, setExpandedDescriptions] = useState<Set<string>>(
92+
new Set(),
93+
);
94+
95+
const MAX_DESCRIPTION_LENGTH = 200;
96+
97+
const truncateDescription = (description: string): string => {
98+
if (description.length <= MAX_DESCRIPTION_LENGTH) {
99+
return description;
100+
}
101+
const truncated = description.slice(0, MAX_DESCRIPTION_LENGTH);
102+
const lastSpace = truncated.lastIndexOf(" ");
103+
if (lastSpace > MAX_DESCRIPTION_LENGTH - 20 && lastSpace > 0) {
104+
return `${truncated.slice(0, lastSpace)}...`;
105+
}
106+
return `${truncated}...`;
107+
};
83108

84109
// Check for stuck deployment (more than 9 minutes) - only for the most recent deployment
85110
const stuckDeployment = useMemo(() => {
@@ -217,118 +242,164 @@ export const ShowDeployments = ({
217242
</div>
218243
) : (
219244
<div className="flex flex-col gap-4">
220-
{deployments?.map((deployment, index) => (
221-
<div
222-
key={deployment.deploymentId}
223-
className="flex items-center justify-between rounded-lg border p-4 gap-2"
224-
>
225-
<div className="flex flex-col">
226-
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
227-
{index + 1}. {deployment.status}
228-
<StatusTooltip
229-
status={deployment?.status}
230-
className="size-2.5"
231-
/>
232-
</span>
233-
<span className="text-sm text-muted-foreground">
234-
{deployment.title}
235-
</span>
236-
{deployment.description && (
237-
<span className="break-all text-sm text-muted-foreground">
238-
{deployment.description}
245+
{deployments?.map((deployment, index) => {
246+
const titleText = deployment?.title?.trim() || "";
247+
const needsTruncation = titleText.length > MAX_DESCRIPTION_LENGTH;
248+
const isExpanded = expandedDescriptions.has(
249+
deployment.deploymentId,
250+
);
251+
252+
return (
253+
<div
254+
key={deployment.deploymentId}
255+
className="flex items-center justify-between rounded-lg border p-4 gap-2"
256+
>
257+
<div className="flex flex-col">
258+
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
259+
{index + 1}. {deployment.status}
260+
<StatusTooltip
261+
status={deployment?.status}
262+
className="size-2.5"
263+
/>
239264
</span>
240-
)}
241-
</div>
242-
<div className="flex flex-col items-end gap-2">
243-
<div className="text-sm capitalize text-muted-foreground flex items-center gap-2">
244-
<DateTooltip date={deployment.createdAt} />
245-
{deployment.startedAt && deployment.finishedAt && (
246-
<Badge
247-
variant="outline"
248-
className="text-[10px] gap-1 flex items-center"
249-
>
250-
<Clock className="size-3" />
251-
{formatDuration(
252-
Math.floor(
253-
(new Date(deployment.finishedAt).getTime() -
254-
new Date(deployment.startedAt).getTime()) /
255-
1000,
256-
),
257-
)}
258-
</Badge>
259-
)}
260-
</div>
261265

262-
<div className="flex flex-row items-center gap-2">
263-
{deployment.pid && deployment.status === "running" && (
264-
<DialogAction
265-
title="Kill Process"
266-
description="Are you sure you want to kill the process?"
267-
type="default"
268-
onClick={async () => {
269-
await killProcess({
270-
deploymentId: deployment.deploymentId,
271-
})
272-
.then(() => {
273-
toast.success("Process killed successfully");
274-
})
275-
.catch(() => {
276-
toast.error("Error killing process");
277-
});
278-
}}
279-
>
280-
<Button
281-
variant="destructive"
282-
size="sm"
283-
isLoading={isKillingProcess}
266+
<div className="flex flex-col gap-1">
267+
<span className="break-words text-sm text-muted-foreground whitespace-pre-wrap">
268+
{isExpanded || !needsTruncation
269+
? titleText
270+
: truncateDescription(titleText)}
271+
</span>
272+
{needsTruncation && (
273+
<button
274+
type="button"
275+
onClick={() => {
276+
const next = new Set(expandedDescriptions);
277+
if (next.has(deployment.deploymentId)) {
278+
next.delete(deployment.deploymentId);
279+
} else {
280+
next.add(deployment.deploymentId);
281+
}
282+
setExpandedDescriptions(next);
283+
}}
284+
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors w-fit mt-1 cursor-pointer"
285+
aria-label={
286+
isExpanded
287+
? "Collapse commit message"
288+
: "Expand commit message"
289+
}
284290
>
285-
Kill Process
286-
</Button>
287-
</DialogAction>
288-
)}
289-
<Button
290-
onClick={() => {
291-
setActiveLog(deployment);
292-
}}
293-
>
294-
View
295-
</Button>
291+
{isExpanded ? (
292+
<>
293+
<ChevronUp className="size-3" />
294+
Show less
295+
</>
296+
) : (
297+
<>
298+
<ChevronDown className="size-3" />
299+
Show more
300+
</>
301+
)}
302+
</button>
303+
)}
304+
{/* Hash (from description) - shown in compact form */}
305+
{deployment.description?.trim() && (
306+
<span className="text-xs text-muted-foreground font-mono">
307+
{deployment.description}
308+
</span>
309+
)}
310+
</div>
311+
</div>
312+
<div className="flex flex-col items-end gap-2 max-w-[300px] w-full justify-start">
313+
<div className="text-sm capitalize text-muted-foreground flex items-center gap-2">
314+
<DateTooltip date={deployment.createdAt} />
315+
{deployment.startedAt && deployment.finishedAt && (
316+
<Badge
317+
variant="outline"
318+
className="text-[10px] gap-1 flex items-center"
319+
>
320+
<Clock className="size-3" />
321+
{formatDuration(
322+
Math.floor(
323+
(new Date(deployment.finishedAt).getTime() -
324+
new Date(deployment.startedAt).getTime()) /
325+
1000,
326+
),
327+
)}
328+
</Badge>
329+
)}
330+
</div>
296331

297-
{deployment?.rollback &&
298-
deployment.status === "done" &&
299-
type === "application" && (
332+
<div className="flex flex-row items-center gap-2">
333+
{deployment.pid && deployment.status === "running" && (
300334
<DialogAction
301-
title="Rollback to this deployment"
302-
description="Are you sure you want to rollback to this deployment?"
335+
title="Kill Process"
336+
description="Are you sure you want to kill the process?"
303337
type="default"
304338
onClick={async () => {
305-
await rollback({
306-
rollbackId: deployment.rollback.rollbackId,
339+
await killProcess({
340+
deploymentId: deployment.deploymentId,
307341
})
308342
.then(() => {
309-
toast.success(
310-
"Rollback initiated successfully",
311-
);
343+
toast.success("Process killed successfully");
312344
})
313345
.catch(() => {
314-
toast.error("Error initiating rollback");
346+
toast.error("Error killing process");
315347
});
316348
}}
317349
>
318350
<Button
319-
variant="secondary"
351+
variant="destructive"
320352
size="sm"
321-
isLoading={isRollingBack}
353+
isLoading={isKillingProcess}
322354
>
323-
<RefreshCcw className="size-4 text-primary group-hover:text-red-500" />
324-
Rollback
355+
Kill Process
325356
</Button>
326357
</DialogAction>
327358
)}
359+
<Button
360+
onClick={() => {
361+
setActiveLog(deployment);
362+
}}
363+
>
364+
View
365+
</Button>
366+
367+
{deployment?.rollback &&
368+
deployment.status === "done" &&
369+
type === "application" && (
370+
<DialogAction
371+
title="Rollback to this deployment"
372+
description="Are you sure you want to rollback to this deployment?"
373+
type="default"
374+
onClick={async () => {
375+
await rollback({
376+
rollbackId: deployment.rollback.rollbackId,
377+
})
378+
.then(() => {
379+
toast.success(
380+
"Rollback initiated successfully",
381+
);
382+
})
383+
.catch(() => {
384+
toast.error("Error initiating rollback");
385+
});
386+
}}
387+
>
388+
<Button
389+
variant="secondary"
390+
size="sm"
391+
isLoading={isRollingBack}
392+
>
393+
<RefreshCcw className="size-4 text-primary group-hover:text-red-500" />
394+
Rollback
395+
</Button>
396+
</DialogAction>
397+
)}
398+
</div>
328399
</div>
329400
</div>
330-
</div>
331-
))}
401+
);
402+
})}
332403
</div>
333404
)}
334405
<ShowDeployment

0 commit comments

Comments
 (0)