diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx
index 92fc337f..d05bbba2 100644
--- a/apps/dokploy/components/dashboard/projects/show.tsx
+++ b/apps/dokploy/components/dashboard/projects/show.tsx
@@ -1,35 +1,35 @@
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";
@@ -38,253 +38,257 @@ import { ProjectEnviroment } from "./project-enviroment";
import { UpdateProject } from "./update";
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 (
- <>
- {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 (
+ <>
+ {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 totalServices =
- project?.mariadb.length +
- project?.mongo.length +
- project?.mysql.length +
- project?.postgres.length +
- project?.redis.length +
- project?.applications.length +
- project?.compose.length;
+ const totalServices =
+ project?.mariadb.length +
+ project?.mongo.length +
+ project?.mysql.length +
+ project?.postgres.length +
+ project?.redis.length +
+ project?.applications.length +
+ project?.compose.length;
- const flattedDomains = [
- ...project.applications.flatMap((a) => a.domains),
- ...project.compose.flatMap((a) => a.domains),
- ];
+ const flattedDomains = [
+ ...project.applications.flatMap((a) => a.domains),
+ ...project.compose.flatMap((a) => a.domains),
+ ];
- 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 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;
- return (
-
-
-
- {flattedDomains.length > 1 ? (
-
-
-
-
-
-
- e.stopPropagation()}
- >
- {renderDomainsDropdown(project.applications)}
- {renderDomainsDropdown(project.compose)}
-
-
- ) : flattedDomains[0] ? (
- e.stopPropagation()}
- >
-
-
-
-
- ) : null}
+ return (
+
+
+
+ {flattedDomains.length > 1 ? (
+
+
+
+
+
+
+ e.stopPropagation()}
+ >
+ {renderDomainsDropdown(project.applications)}
+ {renderDomainsDropdown(project.compose)}
+
+
+ ) : flattedDomains[0] ? (
+ e.stopPropagation()}
+ >
+
+
+
+
+ ) : null}
-
-
-
-
-
-
- {project.name}
-
-
+
+
+
+
+
+
+ {project.name}
+
+
-
- {project.description}
-
-
-
-
-
-
-
-
-
-
-
- Actions
-
- e.stopPropagation()}>
-
-
- e.stopPropagation()}>
-
-
+
+ {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..8afea672
--- /dev/null
+++ b/apps/dokploy/components/dashboard/search-command.tsx
@@ -0,0 +1,189 @@
+"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";
+import { api } from "@/utils/api";
+import { Badge } from "@/components/ui/badge";
+import { StatusTooltip } from "../shared/status-tooltip";
+
+type Project = Awaited>;
+
+export const SearchCommand = () => {
+ const router = useRouter();
+ const [open, setOpen] = React.useState(false);
+ const [search, setSearch] = React.useState("");
+
+ const { data } = api.project.all.useQuery();
+ const { data: isCloud, isLoading } = api.settings.isCloud.useQuery();
+
+ 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);
+ }, []);
+
+ return (
+
+
+
+
+
+ No projects added yet. Click on Create project.
+
+
+
+ {data?.map((project) => (
+ {
+ router.push(`/dashboard/project/${project.projectId}`);
+ setOpen(false);
+ }}
+ >
+
+ {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}`
+ );
+ setOpen(false);
+ }}
+ >
+ {application.type === "postgres" && (
+
+ )}
+ {application.type === "redis" && (
+
+ )}
+ {application.type === "mariadb" && (
+
+ )}
+ {application.type === "mongo" && (
+
+ )}
+ {application.type === "mysql" && (
+
+ )}
+ {application.type === "application" && (
+
+ )}
+ {application.type === "compose" && (
+
+ )}
+
+ {project.name} / {application.name}{" "}
+ {application.id}
+
+
+
+
+
+ ));
+ })}
+
+
+
+
+ {
+ router.push("/dashboard/projects");
+ setOpen(false);
+ }}
+ >
+ Projects
+
+ {!isCloud && (
+ <>
+ {
+ router.push("/dashboard/monitoring");
+ setOpen(false);
+ }}
+ >
+ Monitoring
+
+ {
+ router.push("/dashboard/traefik");
+ setOpen(false);
+ }}
+ >
+ Traefik
+
+ {
+ router.push("/dashboard/docker");
+ setOpen(false);
+ }}
+ >
+ Docker
+
+ {
+ router.push("/dashboard/requests");
+ setOpen(false);
+ }}
+ >
+ Requests
+
+ >
+ )}
+ {
+ router.push("/dashboard/settings/server");
+ setOpen(false);
+ }}
+ >
+ Settings
+
+
+
+
+
+ );
+};
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 && (
-
- )}
+ return (
+ <>
+
+
+ Dokploy
+
+ {process.env.NEXT_PUBLIC_UMAMI_HOST &&
+ process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID && (
+
+ )}
-
-
- {getLayout( )}
-
- >
- );
+
+
+
+ {getLayout( )}
+
+ >
+ );
};
export default api.withTRPC(
- appWithTranslation(
- MyApp,
- // keep this in sync with next-i18next.config.js
- // if you want to know why don't just import the config file, this because next-i18next.config.js must be a CJS, but the rest of the code is ESM.
- // Add the config here is due to the issue: https://github.com/i18next/next-i18next/issues/2259
- // if one day every page is translated, we can safely remove this config.
- {
- i18n: {
- defaultLocale: "en",
- locales: Object.values(Languages),
- localeDetection: false,
- },
- fallbackLng: "en",
- keySeparator: false,
- },
- ),
+ appWithTranslation(
+ MyApp,
+ // keep this in sync with next-i18next.config.js
+ // if you want to know why don't just import the config file, this because next-i18next.config.js must be a CJS, but the rest of the code is ESM.
+ // Add the config here is due to the issue: https://github.com/i18next/next-i18next/issues/2259
+ // if one day every page is translated, we can safely remove this config.
+ {
+ i18n: {
+ defaultLocale: "en",
+ locales: Object.values(Languages),
+ localeDetection: false,
+ },
+ fallbackLng: "en",
+ keySeparator: false,
+ }
+ )
);