Merge pull request #4336 from Dokploy/fix/template-fetch-timeout-error-handling

fix(templates): add fetch timeout and handle network errors gracefully
This commit is contained in:
Mauricio Siu
2026-04-30 18:52:45 -06:00
committed by GitHub
2 changed files with 43 additions and 44 deletions

View File

@@ -700,11 +700,14 @@ export const composeRouter = createTRPCRouter({
getTags: protectedProcedure getTags: protectedProcedure
.input(z.object({ baseUrl: z.string().optional() })) .input(z.object({ baseUrl: z.string().optional() }))
.query(async ({ input }) => { .query(async ({ input }) => {
const githubTemplates = await fetchTemplatesList(input.baseUrl); try {
const githubTemplates = await fetchTemplatesList(input.baseUrl);
const allTags = githubTemplates.flatMap((template) => template.tags); const allTags = githubTemplates.flatMap((template) => template.tags);
const uniqueTags = _.uniq(allTags); return _.uniq(allTags);
return uniqueTags; } catch (error) {
console.warn("Failed to fetch template tags:", error);
return [];
}
}), }),
disconnectGitProvider: protectedProcedure disconnectGitProvider: protectedProcedure
.input(apiFindCompose) .input(apiFindCompose)

View File

@@ -55,25 +55,22 @@ interface TemplateMetadata {
export async function fetchTemplatesList( export async function fetchTemplatesList(
baseUrl = "https://templates.dokploy.com", baseUrl = "https://templates.dokploy.com",
): Promise<TemplateMetadata[]> { ): Promise<TemplateMetadata[]> {
try { const response = await fetch(`${baseUrl}/meta.json`, {
const response = await fetch(`${baseUrl}/meta.json`); signal: AbortSignal.timeout(10000),
if (!response.ok) { });
throw new Error(`Failed to fetch templates: ${response.statusText}`); if (!response.ok) {
} throw new Error(`Failed to fetch templates: ${response.statusText}`);
const templates = (await response.json()) as TemplateMetadata[];
return templates.map((template) => ({
id: template.id,
name: template.name,
description: template.description,
version: template.version,
logo: template.logo,
links: template.links,
tags: template.tags,
}));
} catch (error) {
console.error("Error fetching templates list:", error);
throw error;
} }
const templates = (await response.json()) as TemplateMetadata[];
return templates.map((template) => ({
id: template.id,
name: template.name,
description: template.description,
version: template.version,
logo: template.logo,
links: template.links,
tags: template.tags,
}));
} }
/** /**
@@ -83,27 +80,26 @@ export async function fetchTemplateFiles(
templateId: string, templateId: string,
baseUrl = "https://templates.dokploy.com", baseUrl = "https://templates.dokploy.com",
): Promise<{ config: CompleteTemplate; dockerCompose: string }> { ): Promise<{ config: CompleteTemplate; dockerCompose: string }> {
try { const timeout = AbortSignal.timeout(10000);
// Fetch both files in parallel const [templateYmlResponse, dockerComposeResponse] = await Promise.all([
const [templateYmlResponse, dockerComposeResponse] = await Promise.all([ fetch(`${baseUrl}/blueprints/${templateId}/template.toml`, {
fetch(`${baseUrl}/blueprints/${templateId}/template.toml`), signal: timeout,
fetch(`${baseUrl}/blueprints/${templateId}/docker-compose.yml`), }),
]); fetch(`${baseUrl}/blueprints/${templateId}/docker-compose.yml`, {
signal: timeout,
}),
]);
if (!templateYmlResponse.ok || !dockerComposeResponse.ok) { if (!templateYmlResponse.ok || !dockerComposeResponse.ok) {
throw new Error("Template files not found"); throw new Error("Template files not found");
}
const [templateYml, dockerCompose] = await Promise.all([
templateYmlResponse.text(),
dockerComposeResponse.text(),
]);
const config = parse(templateYml) as CompleteTemplate;
return { config, dockerCompose };
} catch (error) {
console.error(`Error fetching template ${templateId}:`, error);
throw error;
} }
const [templateYml, dockerCompose] = await Promise.all([
templateYmlResponse.text(),
dockerComposeResponse.text(),
]);
const config = parse(templateYml) as CompleteTemplate;
return { config, dockerCompose };
} }