From d0f54f20674da6d763dc9a8472c1f427f31f6fb2 Mon Sep 17 00:00:00 2001 From: Vlad Vladov Date: Wed, 3 Sep 2025 18:33:42 +0300 Subject: [PATCH 1/3] feat(input): Add focus by Cmd + K shortcut to search input --- .../components/dashboard/projects/show.tsx | 5 +-- .../shared/focus-shortcut-input.tsx | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 apps/dokploy/components/shared/focus-shortcut-input.tsx diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 8577eee8c..fafa08ffc 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -44,7 +44,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; +import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input"; import { Select, SelectContent, @@ -144,12 +144,13 @@ export const ShowProjects = () => { <>
- setSearchQuery(e.target.value)} className="pr-10" /> +
diff --git a/apps/dokploy/components/shared/focus-shortcut-input.tsx b/apps/dokploy/components/shared/focus-shortcut-input.tsx new file mode 100644 index 000000000..9c9215236 --- /dev/null +++ b/apps/dokploy/components/shared/focus-shortcut-input.tsx @@ -0,0 +1,36 @@ +import { useEffect, useRef } from "react"; +import { Input } from "@/components/ui/input"; + +type Props = React.ComponentPropsWithoutRef; + +export const FocusShortcutInput = (props: Props) => { + const inputRef = useRef(null); + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + const isMod = e.metaKey || e.ctrlKey; + if (!isMod || e.key.toLowerCase() !== "k") return; + + const target = e.target as HTMLElement | null; + if (target) { + const tag = target.tagName; + if ( + target.isContentEditable || + tag === "INPUT" || + tag === "TEXTAREA" || + tag === "SELECT" || + target.getAttribute("role") === "textbox" + ) + return; + } + + e.preventDefault(); + inputRef.current?.focus(); + }; + + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, []); + + return ; +}; From 2d41db7f37a33a3def65bcdd9cb8012f7d3173d1 Mon Sep 17 00:00:00 2001 From: Vlad Vladov Date: Fri, 5 Sep 2025 18:19:16 +0300 Subject: [PATCH 2/3] feat(input): Replace Input with FocusShortcutInput --- .../project/[projectId]/environment/[environmentId].tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx index f87ee8687..100b6f2e3 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx @@ -80,7 +80,6 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, @@ -96,6 +95,7 @@ import { import { cn } from "@/lib/utils"; import { appRouter } from "@/server/api/root"; import { api } from "@/utils/api"; +import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input"; export type Services = { appName: string; @@ -1197,7 +1197,7 @@ const EnvironmentPage = (
- setSearchQuery(e.target.value)} From 57dc24bcb1193e22cc1eba52f5cbe53fdf6f8305 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 6 Sep 2025 14:30:59 -0600 Subject: [PATCH 3/3] feat(search-command): enhance service extraction and project navigation - Introduced a new function `extractAllServicesFromProject` to aggregate services from all environments within a project, including environment details. - Updated the project selection logic to navigate to the production environment of a project. - Modified the display of services to include the environment name alongside the service name in the search results. --- .../components/dashboard/search-command.tsx | 78 ++++++++++++++----- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/apps/dokploy/components/dashboard/search-command.tsx b/apps/dokploy/components/dashboard/search-command.tsx index 511ca00ab..42430b6d0 100644 --- a/apps/dokploy/components/dashboard/search-command.tsx +++ b/apps/dokploy/components/dashboard/search-command.tsx @@ -3,6 +3,10 @@ import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react"; import { useRouter } from "next/router"; import React from "react"; +import { + extractServices, + type Services, +} from "@/components/dashboard/settings/users/add-permissions"; import { MariadbIcon, MongodbIcon, @@ -20,13 +24,34 @@ import { CommandSeparator, } from "@/components/ui/command"; import { authClient } from "@/lib/auth-client"; -// import { -// extractServices, -// type Services, -// } from "@/pages/dashboard/project/[projectId]"; import { api } from "@/utils/api"; import { StatusTooltip } from "../shared/status-tooltip"; +// Extended Services type to include environmentId and environmentName for search navigation +type SearchServices = Services & { + environmentId: string; + environmentName: string; +}; + +const extractAllServicesFromProject = (project: any): SearchServices[] => { + const allServices: SearchServices[] = []; + + // Iterate through all environments in the project + project.environments?.forEach((environment: any) => { + const environmentServices = extractServices(environment); + const servicesWithEnvironmentId: SearchServices[] = environmentServices.map( + (service) => ({ + ...service, + environmentId: environment.environmentId, + environmentName: environment.name, + }), + ); + allServices.push(...servicesWithEnvironmentId); + }); + + return allServices; +}; + export const SearchCommand = () => { const router = useRouter(); const [open, setOpen] = React.useState(false); @@ -51,7 +76,7 @@ export const SearchCommand = () => { return (
- {/* + { - {data?.map((project) => ( - { - router.push(`/dashboard/project/${project.projectId}`); - setOpen(false); - }} - > - - {project.name} - - ))} + {data?.map((project) => { + console.log("project", project); + const productionEnvironment = project.environments.find( + (environment) => environment.name === "production", + ); + + if (!productionEnvironment) return null; + + return ( + { + router.push( + `/dashboard/project/${project.projectId}/environment/${productionEnvironment!.environmentId}`, + ); + setOpen(false); + }} + > + + {project.name} / {productionEnvironment!.name} + + ); + })} {data?.map((project) => { - const applications: Services[] = extractServices(project); + const applications: SearchServices[] = + extractAllServicesFromProject(project); return applications.map((application) => ( { )} - {project.name} / {application.name}{" "} + {project.name} / {application.environmentName} /{" "} + {application.name}{" "}
{application.id}
@@ -181,7 +219,7 @@ export const SearchCommand = () => { - */} +
); };