Skip to content

Commit 4105353

Browse files
committed
Merge branch 'canary' into template-erpnext
2 parents 829e77a + 8a97107 commit 4105353

File tree

22 files changed

+514
-98
lines changed

22 files changed

+514
-98
lines changed

apps/dokploy/components/dashboard/project/add-template.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ export const AddTemplate = ({ projectId }: Props) => {
308308
{/* Create Button */}
309309
<div
310310
className={cn(
311-
"flex-none px-6 pb-6 pt-3 mt-auto",
311+
"flex-none px-6 py-3 mt-auto",
312312
viewMode === "detailed"
313313
? "flex items-center justify-between bg-muted/30 border-t"
314314
: "flex justify-center",

apps/dokploy/components/dashboard/projects/show.tsx

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import {
2323
import {
2424
DropdownMenu,
2525
DropdownMenuContent,
26+
DropdownMenuGroup,
2627
DropdownMenuItem,
2728
DropdownMenuLabel,
29+
DropdownMenuSeparator,
2830
DropdownMenuTrigger,
2931
} from "@/components/ui/dropdown-menu";
3032
import { Input } from "@/components/ui/input";
@@ -149,14 +151,91 @@ export const ShowProjects = () => {
149151
href={`/dashboard/project/${project.projectId}`}
150152
>
151153
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
152-
<Button
153-
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
154-
size="sm"
155-
variant="default"
156-
>
157-
<ExternalLinkIcon className="size-3.5" />
158-
</Button>
159-
154+
{project.applications.length > 0 ||
155+
project.compose.length > 0 ? (
156+
<DropdownMenu>
157+
<DropdownMenuTrigger asChild>
158+
<Button
159+
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
160+
size="sm"
161+
variant="default"
162+
>
163+
<ExternalLinkIcon className="size-3.5" />
164+
</Button>
165+
</DropdownMenuTrigger>
166+
<DropdownMenuContent
167+
className="w-[200px] space-y-2 overflow-y-auto max-h-[400px]"
168+
onClick={(e) => e.stopPropagation()}
169+
>
170+
{project.applications.length > 0 && (
171+
<DropdownMenuGroup>
172+
<DropdownMenuLabel>
173+
Applications
174+
</DropdownMenuLabel>
175+
{project.applications.map((app) => (
176+
<div key={app.applicationId}>
177+
<DropdownMenuSeparator />
178+
<DropdownMenuGroup>
179+
<DropdownMenuLabel className="font-normal capitalize text-xs">
180+
{app.name}
181+
</DropdownMenuLabel>
182+
<DropdownMenuSeparator />
183+
{app.domains.map((domain) => (
184+
<DropdownMenuItem
185+
key={domain.domainId}
186+
asChild
187+
>
188+
<Link
189+
className="space-x-4 text-xs cursor-pointer justify-between"
190+
target="_blank"
191+
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
192+
>
193+
<span>{domain.host}</span>
194+
<ExternalLinkIcon className="size-4 shrink-0" />
195+
</Link>
196+
</DropdownMenuItem>
197+
))}
198+
</DropdownMenuGroup>
199+
</div>
200+
))}
201+
</DropdownMenuGroup>
202+
)}
203+
{project.compose.length > 0 && (
204+
<DropdownMenuGroup>
205+
<DropdownMenuLabel>
206+
Compose
207+
</DropdownMenuLabel>
208+
{project.compose.map((comp) => (
209+
<div key={comp.composeId}>
210+
<DropdownMenuSeparator />
211+
<DropdownMenuGroup>
212+
<DropdownMenuLabel className="font-normal capitalize text-xs">
213+
{comp.name}
214+
</DropdownMenuLabel>
215+
<DropdownMenuSeparator />
216+
{comp.domains.map((domain) => (
217+
<DropdownMenuItem
218+
key={domain.domainId}
219+
asChild
220+
>
221+
<Link
222+
className="space-x-4 text-xs cursor-pointer justify-between"
223+
target="_blank"
224+
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
225+
>
226+
<span>{domain.host}</span>
227+
<ExternalLinkIcon className="size-4 shrink-0" />
228+
</Link>
229+
</DropdownMenuItem>
230+
))}
231+
</DropdownMenuGroup>
232+
</div>
233+
))}
234+
</DropdownMenuGroup>
235+
)}
236+
</DropdownMenuContent>
237+
</DropdownMenu>
238+
) : null}
160239
<CardHeader>
161240
<CardTitle className="flex items-center justify-between gap-2">
162241
<span className="flex flex-col gap-1.5">
@@ -182,7 +261,10 @@ export const ShowProjects = () => {
182261
<MoreHorizontalIcon className="size-5" />
183262
</Button>
184263
</DropdownMenuTrigger>
185-
<DropdownMenuContent className="w-[200px] space-y-2">
264+
<DropdownMenuContent
265+
className="w-[200px] space-y-2 overflow-y-auto max-h-[280px]"
266+
onClick={(e) => e.stopPropagation()}
267+
>
186268
<DropdownMenuLabel className="font-normal">
187269
Actions
188270
</DropdownMenuLabel>

apps/dokploy/components/dashboard/settings/ssh-keys/handle-ssh-keys.tsx

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { Textarea } from "@/components/ui/textarea";
2222
import { sshKeyCreate, type sshKeyType } from "@/server/db/validations";
2323
import { api } from "@/utils/api";
2424
import { zodResolver } from "@hookform/resolvers/zod";
25-
import { PenBoxIcon, PlusIcon } from "lucide-react";
25+
import { DownloadIcon, PenBoxIcon, PlusIcon } from "lucide-react";
2626
import { useEffect, useState } from "react";
2727
import { useForm } from "react-hook-form";
2828
import { toast } from "sonner";
@@ -111,6 +111,26 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
111111
toast.error("Error generating the SSH Key");
112112
});
113113

114+
const downloadKey = (
115+
content: string,
116+
defaultFilename: string,
117+
keyType: "private" | "public",
118+
) => {
119+
const keyName = form.watch("name");
120+
const filename = keyName
121+
? `${keyName}${sshKeyId ? `_${sshKeyId}` : ""}_${keyType}_${defaultFilename}`
122+
: `${keyType}_${defaultFilename}`;
123+
const blob = new Blob([content], { type: "text/plain" });
124+
const url = window.URL.createObjectURL(blob);
125+
const a = document.createElement("a");
126+
a.href = url;
127+
a.download = filename;
128+
document.body.appendChild(a);
129+
a.click();
130+
document.body.removeChild(a);
131+
window.URL.revokeObjectURL(url);
132+
};
133+
114134
return (
115135
<Dialog open={isOpen} onOpenChange={setIsOpen}>
116136
<DialogTrigger className="" asChild>
@@ -245,7 +265,41 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
245265
</FormItem>
246266
)}
247267
/>
248-
<DialogFooter>
268+
<DialogFooter className="flex items-center justify-between">
269+
<div className="flex items-center gap-4">
270+
{form.watch("privateKey") && (
271+
<Button
272+
type="button"
273+
variant="outline"
274+
size="default"
275+
onClick={() =>
276+
downloadKey(form.watch("privateKey"), "id_rsa", "private")
277+
}
278+
className="flex items-center gap-2"
279+
>
280+
<DownloadIcon className="h-4 w-4" />
281+
Private Key
282+
</Button>
283+
)}
284+
{form.watch("publicKey") && (
285+
<Button
286+
type="button"
287+
variant="outline"
288+
size="default"
289+
onClick={() =>
290+
downloadKey(
291+
form.watch("publicKey"),
292+
"id_rsa.pub",
293+
"public",
294+
)
295+
}
296+
className="flex items-center gap-2"
297+
>
298+
<DownloadIcon className="h-4 w-4" />
299+
Public Key
300+
</Button>
301+
)}
302+
</div>
249303
<Button isLoading={isLoading} type="submit">
250304
{sshKeyId ? "Update" : "Create"}
251305
</Button>

apps/dokploy/components/dashboard/settings/web-server/terminal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
44
import { FitAddon } from "xterm-addon-fit";
55
import "@xterm/xterm/css/xterm.css";
66
import { AttachAddon } from "@xterm/addon-attach";
7+
import { ClipboardAddon } from "@xterm/addon-clipboard";
78
import { useTheme } from "next-themes";
89
import { getLocalServerData } from "./local-server-config";
910

@@ -37,6 +38,7 @@ export const Terminal: React.FC<Props> = ({ id, serverId }) => {
3738
foreground: "currentColor",
3839
},
3940
});
41+
4042
const addonFit = new FitAddon();
4143

4244
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
@@ -54,6 +56,8 @@ export const Terminal: React.FC<Props> = ({ id, serverId }) => {
5456

5557
const ws = new WebSocket(wsUrl);
5658
const addonAttach = new AttachAddon(ws);
59+
const clipboardAddon = new ClipboardAddon();
60+
term.loadAddon(clipboardAddon);
5761

5862
// @ts-ignore
5963
term.open(termRef.current);
@@ -68,7 +72,7 @@ export const Terminal: React.FC<Props> = ({ id, serverId }) => {
6872

6973
return (
7074
<div className="flex flex-col gap-4">
71-
<div className="w-full h-full bg-transparent border rounded-lg p-2 ">
75+
<div className="w-full h-full bg-transparent border rounded-lg p-2">
7276
<div id={id} ref={termRef} className="rounded-xl" />
7377
</div>
7478
</div>

apps/dokploy/components/layouts/side.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ export default function Page({ children }: Props) {
783783
</SidebarMenuButton>
784784
</SidebarMenuItem>
785785
))}
786-
{!isCloud && (
786+
{!isCloud && auth?.rol === "admin" && (
787787
<SidebarMenuItem>
788788
<SidebarMenuButton asChild>
789789
<UpdateServerButton />

apps/dokploy/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"@uiw/react-codemirror": "^4.22.1",
7676
"@xterm/addon-attach": "0.10.0",
7777
"@xterm/xterm": "^5.4.0",
78+
"@xterm/addon-clipboard": "0.1.0",
7879
"adm-zip": "^0.5.14",
7980
"bcrypt": "5.1.1",
8081
"bullmq": "5.4.2",

0 commit comments

Comments
 (0)