From 6def84d456b1b058bc7c4cbfde6f5d8a75449dcf Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 9 Mar 2025 14:08:08 -0600 Subject: [PATCH] feat(templates): add custom base URL support for template management - Implement dynamic base URL configuration for template fetching - Add localStorage persistence for base URL - Update template rendering to use dynamic base URL - Modify API routes to support optional base URL parameter - Enhance template browsing flexibility --- .../dashboard/project/add-template.tsx | 128 +++++++++--------- apps/dokploy/server/api/routers/compose.ts | 46 ++++--- packages/server/src/templates/utils/github.ts | 25 ++-- 3 files changed, 100 insertions(+), 99 deletions(-) diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index 43e6f0d9..76a29539 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -66,55 +66,53 @@ import { SearchIcon, } from "lucide-react"; import Link from "next/link"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { toast } from "sonner"; -interface TemplateData { - metadata: { - id: string; - name: string; - description: string; - version: string; - logo: string; - links: { - github: string; - website?: string; - docs?: string; - }; - tags: string[]; - }; - variables: { - [key: string]: string; - }; - config: { - domains: Array<{ - serviceName: string; - port: number; - path?: string; - host?: string; - }>; - env: Record; - mounts?: Array<{ - filePath: string; - content: string; - }>; - }; -} +const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url"; interface Props { projectId: string; + baseUrl?: string; } -export const AddTemplate = ({ projectId }: Props) => { +export const AddTemplate = ({ projectId, baseUrl }: Props) => { const [query, setQuery] = useState(""); const [open, setOpen] = useState(false); const [viewMode, setViewMode] = useState<"detailed" | "icon">("detailed"); const [selectedTags, setSelectedTags] = useState([]); - const { data } = api.compose.templates.useQuery(); + const [customBaseUrl, setCustomBaseUrl] = useState(() => { + // Try to get from props first, then localStorage + if (baseUrl) return baseUrl; + if (typeof window !== "undefined") { + return localStorage.getItem(TEMPLATE_BASE_URL_KEY) || undefined; + } + return undefined; + }); + + // Save to localStorage when customBaseUrl changes + useEffect(() => { + if (customBaseUrl) { + localStorage.setItem(TEMPLATE_BASE_URL_KEY, customBaseUrl); + } else { + localStorage.removeItem(TEMPLATE_BASE_URL_KEY); + } + }, [customBaseUrl]); + + const { data } = api.compose.templates.useQuery( + { baseUrl: customBaseUrl }, + { + enabled: open, + }, + ); const { data: isCloud } = api.settings.isCloud.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery(); - const { data: tags, isLoading: isLoadingTags } = - api.compose.getTags.useQuery(); + const { data: tags, isLoading: isLoadingTags } = api.compose.getTags.useQuery( + { baseUrl: customBaseUrl }, + { + enabled: open, + }, + ); const utils = api.useUtils(); const [serverId, setServerId] = useState(undefined); @@ -125,13 +123,11 @@ export const AddTemplate = ({ projectId }: Props) => { data?.filter((template) => { const matchesTags = selectedTags.length === 0 || - template.metadata.tags.some((tag) => selectedTags.includes(tag)); + template.tags.some((tag) => selectedTags.includes(tag)); const matchesQuery = query === "" || - template.metadata.name.toLowerCase().includes(query.toLowerCase()) || - template.metadata.description - .toLowerCase() - .includes(query.toLowerCase()); + template.name.toLowerCase().includes(query.toLowerCase()) || + template.description.toLowerCase().includes(query.toLowerCase()); return matchesTags && matchesQuery; }) || []; @@ -163,6 +159,14 @@ export const AddTemplate = ({ projectId }: Props) => { className="w-full sm:w-[200px]" value={query} /> + + setCustomBaseUrl(e.target.value || undefined) + } + className="w-full sm:w-[300px]" + value={customBaseUrl || ""} + />