Skip to content

Commit 038df9c

Browse files
authored
Merge pull request #897 from Dokploy/canary
v0.15.0
2 parents 4628930 + 829aa2a commit 038df9c

File tree

135 files changed

+13817
-1405
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+13817
-1405
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ yarn-debug.log*
3434
yarn-error.log*
3535

3636
# Editor
37-
.vscode
3837
.idea
3938

4039
# Misc

apps/dokploy/components/dashboard/application/delete-application.tsx

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Badge } from "@/components/ui/badge";
12
import { Button } from "@/components/ui/button";
23
import {
34
Dialog,
@@ -19,7 +20,7 @@ import {
1920
import { Input } from "@/components/ui/input";
2021
import { api } from "@/utils/api";
2122
import { zodResolver } from "@hookform/resolvers/zod";
22-
import { TrashIcon } from "lucide-react";
23+
import { Copy, TrashIcon } from "lucide-react";
2324
import { useRouter } from "next/router";
2425
import { useState } from "react";
2526
import { useForm } from "react-hook-form";
@@ -102,9 +103,26 @@ export const DeleteApplication = ({ applicationId }: Props) => {
102103
name="projectName"
103104
render={({ field }) => (
104105
<FormItem>
105-
<FormLabel>
106-
To confirm, type "{data?.name}/{data?.appName}" in the box
107-
below
106+
<FormLabel className="flex items-center gap-2">
107+
<span>
108+
To confirm, type{" "}
109+
<Badge
110+
className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer"
111+
variant="outline"
112+
onClick={() => {
113+
if (data?.name && data?.appName) {
114+
navigator.clipboard.writeText(
115+
`${data.name}/${data.appName}`,
116+
);
117+
toast.success("Copied to clipboard. Be careful!");
118+
}
119+
}}
120+
>
121+
{data?.name}/{data?.appName}&nbsp;
122+
<Copy className="h-4 w-4 ml-1 text-muted-foreground" />
123+
</Badge>{" "}
124+
in the box below:
125+
</span>
108126
</FormLabel>
109127
<FormControl>
110128
<Input

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

+51-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import {
66
DialogTitle,
77
} from "@/components/ui/dialog";
88
import { useEffect, useRef, useState } from "react";
9+
import { TerminalLine } from "../../docker/logs/terminal-line";
10+
import { LogLine, parseLogs } from "../../docker/logs/utils";
11+
import { Badge } from "@/components/ui/badge";
12+
import { Loader2 } from "lucide-react";
913

1014
interface Props {
1115
logPath: string | null;
@@ -15,9 +19,26 @@ interface Props {
1519
}
1620
export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
1721
const [data, setData] = useState("");
18-
const endOfLogsRef = useRef<HTMLDivElement>(null);
22+
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
1923
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
24+
const [autoScroll, setAutoScroll] = useState(true);
25+
const scrollRef = useRef<HTMLDivElement>(null);
2026

27+
28+
const scrollToBottom = () => {
29+
if (autoScroll && scrollRef.current) {
30+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
31+
}
32+
};
33+
34+
const handleScroll = () => {
35+
if (!scrollRef.current) return;
36+
37+
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
38+
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
39+
setAutoScroll(isAtBottom);
40+
};
41+
2142
useEffect(() => {
2243
if (!open || !logPath) return;
2344

@@ -48,14 +69,21 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
4869
};
4970
}, [logPath, open]);
5071

51-
const scrollToBottom = () => {
52-
endOfLogsRef.current?.scrollIntoView({ behavior: "smooth" });
53-
};
5472

5573
useEffect(() => {
56-
scrollToBottom();
74+
const logs = parseLogs(data);
75+
setFilteredLogs(logs);
5776
}, [data]);
5877

78+
useEffect(() => {
79+
scrollToBottom();
80+
81+
if (autoScroll && scrollRef.current) {
82+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
83+
}
84+
}, [filteredLogs, autoScroll]);
85+
86+
5987
return (
6088
<Dialog
6189
open={open}
@@ -76,17 +104,27 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
76104
<DialogHeader>
77105
<DialogTitle>Deployment</DialogTitle>
78106
<DialogDescription>
79-
See all the details of this deployment
107+
See all the details of this deployment | <Badge variant="blank" className="text-xs">{filteredLogs.length} lines</Badge>
80108
</DialogDescription>
81109
</DialogHeader>
82110

83-
<div className="text-wrap rounded-lg border p-4 text-sm sm:max-w-[59rem]">
84-
<code>
85-
<pre className="whitespace-pre-wrap break-words">
86-
{data || "Loading..."}
87-
</pre>
88-
<div ref={endOfLogsRef} />
89-
</code>
111+
<div
112+
ref={scrollRef}
113+
onScroll={handleScroll}
114+
className="h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#d4d4d4] dark:bg-[#050506] rounded custom-logs-scrollbar"
115+
> {
116+
filteredLogs.length > 0 ? filteredLogs.map((log: LogLine, index: number) => (
117+
<TerminalLine
118+
key={index}
119+
log={log}
120+
noTimestamp
121+
/>
122+
)) :
123+
(
124+
<div className="flex justify-center items-center h-full text-muted-foreground">
125+
<Loader2 className="h-6 w-6 animate-spin" />
126+
</div>
127+
)}
90128
</div>
91129
</DialogContent>
92130
</Dialog>

apps/dokploy/components/dashboard/application/general/deploy-application.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import {
1111
} from "@/components/ui/alert-dialog";
1212
import { Button } from "@/components/ui/button";
1313
import { api } from "@/utils/api";
14+
import { useRouter } from "next/router";
1415
import { toast } from "sonner";
1516

1617
interface Props {
1718
applicationId: string;
1819
}
1920

2021
export const DeployApplication = ({ applicationId }: Props) => {
22+
const router = useRouter();
2123
const { data, refetch } = api.application.one.useQuery(
2224
{
2325
applicationId,
@@ -51,6 +53,9 @@ export const DeployApplication = ({ applicationId }: Props) => {
5153
.then(async () => {
5254
toast.success("Application deployed succesfully");
5355
await refetch();
56+
router.push(
57+
`/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`,
58+
);
5459
})
5560

5661
.catch(() => {

apps/dokploy/components/dashboard/application/logs/show.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => {
9090
</Select>
9191
<DockerLogs
9292
serverId={serverId || ""}
93-
id="terminal"
9493
containerId={containerId || "select-a-container"}
9594
/>
9695
</CardContent>

apps/dokploy/components/dashboard/compose/delete-compose.tsx

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Badge } from "@/components/ui/badge";
12
import { Button } from "@/components/ui/button";
23
import {
34
Dialog,
@@ -19,6 +20,7 @@ import {
1920
import { Input } from "@/components/ui/input";
2021
import { api } from "@/utils/api";
2122
import { zodResolver } from "@hookform/resolvers/zod";
23+
import { Copy } from "lucide-react";
2224
import { TrashIcon } from "lucide-react";
2325
import { useRouter } from "next/router";
2426
import { useState } from "react";
@@ -100,10 +102,27 @@ export const DeleteCompose = ({ composeId }: Props) => {
100102
name="projectName"
101103
render={({ field }) => (
102104
<FormItem>
103-
<FormLabel>
104-
To confirm, type "{data?.name}/{data?.appName}" in the box
105-
below
106-
</FormLabel>{" "}
105+
<FormLabel className="flex items-center gap-2">
106+
<span>
107+
To confirm, type{" "}
108+
<Badge
109+
className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer"
110+
variant="outline"
111+
onClick={() => {
112+
if (data?.name && data?.appName) {
113+
navigator.clipboard.writeText(
114+
`${data.name}/${data.appName}`,
115+
);
116+
toast.success("Copied to clipboard. Be careful!");
117+
}
118+
}}
119+
>
120+
{data?.name}/{data?.appName}&nbsp;
121+
<Copy className="h-4 w-4 ml-1 text-muted-foreground" />
122+
</Badge>{" "}
123+
in the box below:
124+
</span>
125+
</FormLabel>
107126
<FormControl>
108127
<Input
109128
placeholder="Enter compose name to confirm"

apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx

+56-14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import {
66
DialogTitle,
77
} from "@/components/ui/dialog";
88
import { useEffect, useRef, useState } from "react";
9+
import { TerminalLine } from "../../docker/logs/terminal-line";
10+
import { LogLine, parseLogs } from "../../docker/logs/utils";
11+
import { Badge } from "@/components/ui/badge";
12+
import { Loader2 } from "lucide-react";
13+
914

1015
interface Props {
1116
logPath: string | null;
@@ -20,9 +25,26 @@ export const ShowDeploymentCompose = ({
2025
serverId,
2126
}: Props) => {
2227
const [data, setData] = useState("");
23-
const endOfLogsRef = useRef<HTMLDivElement>(null);
28+
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
2429
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
30+
const [autoScroll, setAutoScroll] = useState(true);
31+
const scrollRef = useRef<HTMLDivElement>(null);
2532

33+
const scrollToBottom = () => {
34+
if (autoScroll && scrollRef.current) {
35+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
36+
}
37+
};
38+
39+
const handleScroll = () => {
40+
if (!scrollRef.current) return;
41+
42+
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
43+
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
44+
setAutoScroll(isAtBottom);
45+
};
46+
47+
2648
useEffect(() => {
2749
if (!open || !logPath) return;
2850

@@ -54,14 +76,20 @@ export const ShowDeploymentCompose = ({
5476
};
5577
}, [logPath, open]);
5678

57-
const scrollToBottom = () => {
58-
endOfLogsRef.current?.scrollIntoView({ behavior: "smooth" });
59-
};
6079

6180
useEffect(() => {
62-
scrollToBottom();
81+
const logs = parseLogs(data);
82+
setFilteredLogs(logs);
6383
}, [data]);
6484

85+
useEffect(() => {
86+
scrollToBottom();
87+
88+
if (autoScroll && scrollRef.current) {
89+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
90+
}
91+
}, [filteredLogs, autoScroll]);
92+
6593
return (
6694
<Dialog
6795
open={open}
@@ -78,21 +106,35 @@ export const ShowDeploymentCompose = ({
78106
}
79107
}}
80108
>
81-
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
109+
<DialogContent className={"sm:max-w-5xl max-h-screen"}>
82110
<DialogHeader>
83111
<DialogTitle>Deployment</DialogTitle>
84112
<DialogDescription>
85-
See all the details of this deployment
113+
See all the details of this deployment | <Badge variant="blank" className="text-xs">{filteredLogs.length} lines</Badge>
86114
</DialogDescription>
87115
</DialogHeader>
88116

89-
<div className="text-wrap rounded-lg border p-4 text-sm sm:max-w-[59rem]">
90-
<code>
91-
<pre className="whitespace-pre-wrap break-words">
92-
{data || "Loading..."}
93-
</pre>
94-
<div ref={endOfLogsRef} />
95-
</code>
117+
<div
118+
ref={scrollRef}
119+
onScroll={handleScroll}
120+
className="h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#d4d4d4] dark:bg-[#050506] rounded custom-logs-scrollbar"
121+
>
122+
123+
124+
{
125+
filteredLogs.length > 0 ? filteredLogs.map((log: LogLine, index: number) => (
126+
<TerminalLine
127+
key={index}
128+
log={log}
129+
noTimestamp
130+
/>
131+
)) :
132+
(
133+
<div className="flex justify-center items-center h-full text-muted-foreground">
134+
<Loader2 className="h-6 w-6 animate-spin" />
135+
</div>
136+
)
137+
}
96138
</div>
97139
</DialogContent>
98140
</Dialog>

apps/dokploy/components/dashboard/compose/general/deploy-compose.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import {
1111
} from "@/components/ui/alert-dialog";
1212
import { Button } from "@/components/ui/button";
1313
import { api } from "@/utils/api";
14+
import { useRouter } from "next/router";
1415
import { toast } from "sonner";
1516

1617
interface Props {
1718
composeId: string;
1819
}
1920

2021
export const DeployCompose = ({ composeId }: Props) => {
22+
const router = useRouter();
2123
const { data, refetch } = api.compose.one.useQuery(
2224
{
2325
composeId,
@@ -48,9 +50,15 @@ export const DeployCompose = ({ composeId }: Props) => {
4850
await refetch();
4951
await deploy({
5052
composeId,
51-
}).catch(() => {
52-
toast.error("Error to deploy Compose");
53-
});
53+
})
54+
.then(async () => {
55+
router.push(
56+
`/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`
57+
);
58+
})
59+
.catch(() => {
60+
toast.error("Error to deploy Compose");
61+
});
5462

5563
await refetch();
5664
}}

apps/dokploy/components/dashboard/compose/logs/show.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ export const ShowDockerLogsCompose = ({
9696
</Select>
9797
<DockerLogs
9898
serverId={serverId || ""}
99-
id="terminal"
10099
containerId={containerId || "select-a-container"}
101100
/>
102101
</CardContent>

0 commit comments

Comments
 (0)