From cbf0f37a49c165d21f985f280feb8b88aa934b92 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Tue, 10 Dec 2024 13:37:46 +0100 Subject: [PATCH 01/11] feat: add search command dialog with projects and services --- .../dashboard/projects/search-command.tsx | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 apps/dokploy/components/dashboard/projects/search-command.tsx diff --git a/apps/dokploy/components/dashboard/projects/search-command.tsx b/apps/dokploy/components/dashboard/projects/search-command.tsx new file mode 100644 index 00000000..4070d785 --- /dev/null +++ b/apps/dokploy/components/dashboard/projects/search-command.tsx @@ -0,0 +1,114 @@ +"use client" + +import React from "react"; +import {Command, CommandEmpty, CommandList, CommandGroup, CommandInput, CommandItem,CommandDialog, CommandSeparator} from "@/components/ui/command"; +import { useRouter } from 'next/router' +import {extractServices, type Services} from "@/pages/dashboard/project/[projectId]"; +import type {findProjectById} from "@dokploy/server/services/project"; +import {BookIcon, CircuitBoard, GlobeIcon} from "lucide-react"; +import { + MariadbIcon, + MongodbIcon, + MysqlIcon, + PostgresqlIcon, + RedisIcon, +} from "@/components/icons/data-tools-icons"; + +// export type Services = { +// name: string; +// type: +// | "mariadb" +// | "application" +// | "postgres" +// | "mysql" +// | "mongo" +// | "redis" +// | "compose"; +// description?: string | null; +// id: string; +// createdAt: string; +// status?: "idle" | "running" | "done" | "error"; +// }; + +type Project = Awaited>; + +interface Props { + data: Project[]; +} + +export const SearchCommand = ({data}: Props) => { + const router = useRouter() + const [open, setOpen] = React.useState(false) + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "j" && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + setOpen((open) => !open) + } + } + + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, []) + + console.log("DEBUG: data: ", data); + + return ( +
+ + + + No projects added yet. Click on Create project. + + + {data?.map((project) => ( + router.push(project.projectId)}> + + {project.name} + + ))} + + + + + + {data?.map((project) => { + const applications: Services[] = extractServices(project); + return ( + applications.map((application) => ( + router.push(`/dashboard/project/${project.projectId}/services/${application.type}/${application.id}`)}> + {application.type === "postgres" && ( + + )} + {application.type === "redis" && ( + + )} + {application.type === "mariadb" && ( + + )} + {application.type === "mongo" && ( + + )} + {application.type === "mysql" && ( + + )} + {application.type === "application" && ( + + )} + {application.type === "compose" && ( + + )} + {application.name} + + )) + ); + })} + + + + +
+ ) +} \ No newline at end of file From 84bb98c7e6b4181cc1e1125743a78eab77c60bc4 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Tue, 10 Dec 2024 13:37:57 +0100 Subject: [PATCH 02/11] feat: add search command dialog with projects and services --- apps/dokploy/components/dashboard/projects/show.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 92fc337f..ee36188e 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -36,6 +36,7 @@ import { Fragment } from "react"; import { toast } from "sonner"; import { ProjectEnviroment } from "./project-enviroment"; import { UpdateProject } from "./update"; +import {SearchCommand} from "@/components/dashboard/projects/search-command"; export const ShowProjects = () => { const utils = api.useUtils(); @@ -51,8 +52,14 @@ export const ShowProjects = () => { ); const { mutateAsync } = api.project.remove.useMutation(); + + return ( <> + + {/*@ts-expect-error Type mismatch*/} + + {data?.length === 0 && (
From 029cbf4498329f96b8686cdce2a638fed5c282ce Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Tue, 10 Dec 2024 14:09:04 +0100 Subject: [PATCH 03/11] refactor: change location of search-command --- .../dashboard/projects/search-command.tsx | 114 ---- .../components/dashboard/projects/show.tsx | 541 +++++++++--------- .../components/dashboard/search-command.tsx | 169 ++++++ 3 files changed, 440 insertions(+), 384 deletions(-) delete mode 100644 apps/dokploy/components/dashboard/projects/search-command.tsx create mode 100644 apps/dokploy/components/dashboard/search-command.tsx diff --git a/apps/dokploy/components/dashboard/projects/search-command.tsx b/apps/dokploy/components/dashboard/projects/search-command.tsx deleted file mode 100644 index 4070d785..00000000 --- a/apps/dokploy/components/dashboard/projects/search-command.tsx +++ /dev/null @@ -1,114 +0,0 @@ -"use client" - -import React from "react"; -import {Command, CommandEmpty, CommandList, CommandGroup, CommandInput, CommandItem,CommandDialog, CommandSeparator} from "@/components/ui/command"; -import { useRouter } from 'next/router' -import {extractServices, type Services} from "@/pages/dashboard/project/[projectId]"; -import type {findProjectById} from "@dokploy/server/services/project"; -import {BookIcon, CircuitBoard, GlobeIcon} from "lucide-react"; -import { - MariadbIcon, - MongodbIcon, - MysqlIcon, - PostgresqlIcon, - RedisIcon, -} from "@/components/icons/data-tools-icons"; - -// export type Services = { -// name: string; -// type: -// | "mariadb" -// | "application" -// | "postgres" -// | "mysql" -// | "mongo" -// | "redis" -// | "compose"; -// description?: string | null; -// id: string; -// createdAt: string; -// status?: "idle" | "running" | "done" | "error"; -// }; - -type Project = Awaited>; - -interface Props { - data: Project[]; -} - -export const SearchCommand = ({data}: Props) => { - const router = useRouter() - const [open, setOpen] = React.useState(false) - - React.useEffect(() => { - const down = (e: KeyboardEvent) => { - if (e.key === "j" && (e.metaKey || e.ctrlKey)) { - e.preventDefault() - setOpen((open) => !open) - } - } - - document.addEventListener("keydown", down) - return () => document.removeEventListener("keydown", down) - }, []) - - console.log("DEBUG: data: ", data); - - return ( -
- - - - No projects added yet. Click on Create project. - - - {data?.map((project) => ( - router.push(project.projectId)}> - - {project.name} - - ))} - - - - - - {data?.map((project) => { - const applications: Services[] = extractServices(project); - return ( - applications.map((application) => ( - router.push(`/dashboard/project/${project.projectId}/services/${application.type}/${application.id}`)}> - {application.type === "postgres" && ( - - )} - {application.type === "redis" && ( - - )} - {application.type === "mariadb" && ( - - )} - {application.type === "mongo" && ( - - )} - {application.type === "mysql" && ( - - )} - {application.type === "application" && ( - - )} - {application.type === "compose" && ( - - )} - {application.name} - - )) - ); - })} - - - - -
- ) -} \ No newline at end of file diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index ee36188e..b4c86143 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -1,297 +1,298 @@ import { DateTooltip } from "@/components/shared/date-tooltip"; import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { api } from "@/utils/api"; import { - AlertTriangle, - BookIcon, - ExternalLink, - ExternalLinkIcon, - FolderInput, - MoreHorizontalIcon, - TrashIcon, + AlertTriangle, + BookIcon, + ExternalLink, + ExternalLinkIcon, + FolderInput, + MoreHorizontalIcon, + TrashIcon, } from "lucide-react"; import Link from "next/link"; import { Fragment } from "react"; import { toast } from "sonner"; import { ProjectEnviroment } from "./project-enviroment"; import { UpdateProject } from "./update"; -import {SearchCommand} from "@/components/dashboard/projects/search-command"; +import { SearchCommand } from "@/components/dashboard/search-command"; export const ShowProjects = () => { - const utils = api.useUtils(); - const { data } = api.project.all.useQuery(); - const { data: auth } = api.auth.get.useQuery(); - const { data: user } = api.user.byAuthId.useQuery( - { - authId: auth?.id || "", - }, - { - enabled: !!auth?.id && auth?.rol === "user", - }, - ); - const { mutateAsync } = api.project.remove.useMutation(); + const utils = api.useUtils(); + const { data } = api.project.all.useQuery(); + const { data: auth } = api.auth.get.useQuery(); + const { data: user } = api.user.byAuthId.useQuery( + { + authId: auth?.id || "", + }, + { + enabled: !!auth?.id && auth?.rol === "user", + } + ); + const { mutateAsync } = api.project.remove.useMutation(); + return ( + <> + {/*@ts-expect-error Type mismatch*/} + + {data?.length === 0 && ( +
+ + + No projects added yet. Click on Create project. + +
+ )} +
+ {data?.map((project) => { + const emptyServices = + project?.mariadb.length === 0 && + project?.mongo.length === 0 && + project?.mysql.length === 0 && + project?.postgres.length === 0 && + project?.redis.length === 0 && + project?.applications.length === 0 && + project?.compose.length === 0; - return ( - <> + const totalServices = + project?.mariadb.length + + project?.mongo.length + + project?.mysql.length + + project?.postgres.length + + project?.redis.length + + project?.applications.length + + project?.compose.length; - {/*@ts-expect-error Type mismatch*/} - + const flattedDomains = [ + ...project.applications.flatMap((a) => a.domains), + ...project.compose.flatMap((a) => a.domains), + ]; - {data?.length === 0 && ( -
- - - No projects added yet. Click on Create project. - -
- )} -
- {data?.map((project) => { - const emptyServices = - project?.mariadb.length === 0 && - project?.mongo.length === 0 && - project?.mysql.length === 0 && - project?.postgres.length === 0 && - project?.redis.length === 0 && - project?.applications.length === 0 && - project?.compose.length === 0; + const renderDomainsDropdown = ( + item: typeof project.compose | typeof project.applications + ) => + item[0] ? ( + + + {"applicationId" in item[0] ? "Applications" : "Compose"} + + {item.map((a) => ( + + + + + {a.name} + + + {a.domains.map((domain) => ( + + + {domain.host} + + + + ))} + + + ))} + + ) : null; - const totalServices = - project?.mariadb.length + - project?.mongo.length + - project?.mysql.length + - project?.postgres.length + - project?.redis.length + - project?.applications.length + - project?.compose.length; + return ( +
+ + + {flattedDomains.length > 1 ? ( + + + + + e.stopPropagation()} + > + {renderDomainsDropdown(project.applications)} + {renderDomainsDropdown(project.compose)} + + + ) : flattedDomains[0] ? ( + + ) : null} - const flattedDomains = [ - ...project.applications.flatMap((a) => a.domains), - ...project.compose.flatMap((a) => a.domains), - ]; + + + +
+ + + {project.name} + +
- const renderDomainsDropdown = ( - item: typeof project.compose | typeof project.applications, - ) => - item[0] ? ( - - - {"applicationId" in item[0] ? "Applications" : "Compose"} - - {item.map((a) => ( - - - - - {a.name} - - - {a.domains.map((domain) => ( - - - {domain.host} - - - - ))} - - - ))} - - ) : null; + + {project.description} + +
+
+ + + + + + + Actions + +
e.stopPropagation()}> + +
+
e.stopPropagation()}> + +
- return ( -
- - - {flattedDomains.length > 1 ? ( - - - - - e.stopPropagation()} - > - {renderDomainsDropdown(project.applications)} - {renderDomainsDropdown(project.compose)} - - - ) : flattedDomains[0] ? ( - - ) : null} - - - - -
- - - {project.name} - -
- - - {project.description} - -
-
- - - - - - - Actions - -
e.stopPropagation()}> - -
-
e.stopPropagation()}> - -
- -
e.stopPropagation()}> - {(auth?.rol === "admin" || - user?.canDeleteProjects) && ( - - - e.preventDefault()} - > - - Delete - - - - - - Are you sure to delete this project? - - {!emptyServices ? ( -
- - - You have active services, please - delete them first - -
- ) : ( - - This action cannot be undone - - )} -
- - - Cancel - - { - await mutateAsync({ - projectId: project.projectId, - }) - .then(() => { - toast.success( - "Project delete succesfully", - ); - }) - .catch(() => { - toast.error( - "Error to delete this project", - ); - }) - .finally(() => { - utils.project.all.invalidate(); - }); - }} - > - Delete - - -
-
- )} -
-
-
-
-
-
- -
- - Created - - - {totalServices}{" "} - {totalServices === 1 ? "service" : "services"} - -
-
-
- -
- ); - })} -
- - ); +
e.stopPropagation()}> + {(auth?.rol === "admin" || + user?.canDeleteProjects) && ( + + + e.preventDefault()} + > + + Delete + + + + + + Are you sure to delete this project? + + {!emptyServices ? ( +
+ + + You have active services, please + delete them first + +
+ ) : ( + + This action cannot be undone + + )} +
+ + + Cancel + + { + await mutateAsync({ + projectId: project.projectId, + }) + .then(() => { + toast.success( + "Project delete succesfully" + ); + }) + .catch(() => { + toast.error( + "Error to delete this project" + ); + }) + .finally(() => { + utils.project.all.invalidate(); + }); + }} + > + Delete + + +
+
+ )} +
+ + +
+ + + +
+ + Created + + + {totalServices}{" "} + {totalServices === 1 ? "service" : "services"} + +
+
+ + +
+ ); + })} +
+ + ); }; diff --git a/apps/dokploy/components/dashboard/search-command.tsx b/apps/dokploy/components/dashboard/search-command.tsx new file mode 100644 index 00000000..69813d7f --- /dev/null +++ b/apps/dokploy/components/dashboard/search-command.tsx @@ -0,0 +1,169 @@ +"use client"; + +import React from "react"; +import { + Command, + CommandEmpty, + CommandList, + CommandGroup, + CommandInput, + CommandItem, + CommandDialog, + CommandSeparator, +} from "@/components/ui/command"; +import { useRouter } from "next/router"; +import { + extractServices, + type Services, +} from "@/pages/dashboard/project/[projectId]"; +import type { findProjectById } from "@dokploy/server/services/project"; +import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react"; +import { + MariadbIcon, + MongodbIcon, + MysqlIcon, + PostgresqlIcon, + RedisIcon, +} from "@/components/icons/data-tools-icons"; + +type Project = Awaited>; + +interface Props { + data: Project[]; +} + +export const SearchCommand = ({ data }: Props) => { + const router = useRouter(); + const [open, setOpen] = React.useState(false); + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "j" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; + + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); + + console.log("DEBUG: data: ", data); + + return ( +
+ + + + + No projects added yet. Click on Create project. + + + { + router.push("/dashboard/projects"); + setOpen(false); + }} + > + Projects + + { + router.push("/dashboard/monitoring"); + setOpen(false); + }} + > + Monitoring + + { + router.push("/dashboard/traefik"); + setOpen(false); + }} + > + Treafik + + { + router.push("/dashboard/docker"); + setOpen(false); + }} + > + Docker + + { + router.push("/dashboard/requests"); + setOpen(false); + }} + > + Requests + + { + router.push("/dashboard/profile"); + setOpen(false); + }} + > + Settings + + + + + {data?.map((project) => ( + router.push(project.projectId)} + > + + {project.name} + + ))} + + + + + + {data?.map((project) => { + const applications: Services[] = extractServices(project); + return applications.map((application) => ( + + router.push( + `/dashboard/project/${project.projectId}/services/${application.type}/${application.id}` + ) + } + > + {application.type === "postgres" && ( + + )} + {application.type === "redis" && ( + + )} + {application.type === "mariadb" && ( + + )} + {application.type === "mongo" && ( + + )} + {application.type === "mysql" && ( + + )} + {application.type === "application" && ( + + )} + {application.type === "compose" && ( + + )} + {application.name} + + )); + })} + + + + +
+ ); +}; From 54b6a850b7dece9daeec6205cc361f643cf7c370 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Tue, 10 Dec 2024 14:15:39 +0100 Subject: [PATCH 04/11] refactor: make command globally available --- .../components/dashboard/projects/show.tsx | 4 - .../components/dashboard/search-command.tsx | 113 +++++++++--------- apps/dokploy/pages/_app.tsx | 106 ++++++++-------- 3 files changed, 111 insertions(+), 112 deletions(-) diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index b4c86143..d05bbba2 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -36,7 +36,6 @@ import { Fragment } from "react"; import { toast } from "sonner"; import { ProjectEnviroment } from "./project-enviroment"; import { UpdateProject } from "./update"; -import { SearchCommand } from "@/components/dashboard/search-command"; export const ShowProjects = () => { const utils = api.useUtils(); @@ -54,9 +53,6 @@ export const ShowProjects = () => { return ( <> - {/*@ts-expect-error Type mismatch*/} - - {data?.length === 0 && (
diff --git a/apps/dokploy/components/dashboard/search-command.tsx b/apps/dokploy/components/dashboard/search-command.tsx index 69813d7f..3bbacae9 100644 --- a/apps/dokploy/components/dashboard/search-command.tsx +++ b/apps/dokploy/components/dashboard/search-command.tsx @@ -25,17 +25,17 @@ import { PostgresqlIcon, RedisIcon, } from "@/components/icons/data-tools-icons"; +import { api } from "@/utils/api"; +import { truncate } from "lodash"; type Project = Awaited>; -interface Props { - data: Project[]; -} - -export const SearchCommand = ({ data }: Props) => { +export const SearchCommand = () => { const router = useRouter(); const [open, setOpen] = React.useState(false); + const { data } = api.project.all.useQuery(); + React.useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "j" && (e.metaKey || e.ctrlKey)) { @@ -53,61 +53,11 @@ export const SearchCommand = ({ data }: Props) => { return (
- + No projects added yet. Click on Create project. - - { - router.push("/dashboard/projects"); - setOpen(false); - }} - > - Projects - - { - router.push("/dashboard/monitoring"); - setOpen(false); - }} - > - Monitoring - - { - router.push("/dashboard/traefik"); - setOpen(false); - }} - > - Treafik - - { - router.push("/dashboard/docker"); - setOpen(false); - }} - > - Docker - - { - router.push("/dashboard/requests"); - setOpen(false); - }} - > - Requests - - { - router.push("/dashboard/profile"); - setOpen(false); - }} - > - Settings - - {data?.map((project) => ( @@ -162,6 +112,57 @@ export const SearchCommand = ({ data }: Props) => { })} + +
diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx index e7c0befc..f91f378a 100644 --- a/apps/dokploy/pages/_app.tsx +++ b/apps/dokploy/pages/_app.tsx @@ -11,72 +11,74 @@ import { Inter } from "next/font/google"; import Head from "next/head"; import Script from "next/script"; import type { ReactElement, ReactNode } from "react"; +import { SearchCommand } from "@/components/dashboard/search-command"; const inter = Inter({ subsets: ["latin"] }); export type NextPageWithLayout

= NextPage & { - getLayout?: (page: ReactElement) => ReactNode; - // session: Session | null; - theme?: string; + getLayout?: (page: ReactElement) => ReactNode; + // session: Session | null; + theme?: string; }; type AppPropsWithLayout = AppProps & { - Component: NextPageWithLayout; + Component: NextPageWithLayout; }; const MyApp = ({ - Component, - pageProps: { ...pageProps }, + Component, + pageProps: { ...pageProps }, }: AppPropsWithLayout) => { - const getLayout = Component.getLayout ?? ((page) => page); + const getLayout = Component.getLayout ?? ((page) => page); - return ( - <> - - - Dokploy - - {process.env.NEXT_PUBLIC_UMAMI_HOST && - process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID && ( -