Skip to content

Commit ccbfd2c

Browse files
authored
fix: lazy-fetch include file content for container compose tab (#2291)
1 parent a129d96 commit ccbfd2c

File tree

1 file changed

+61
-33
lines changed
  • frontend/src/routes/(app)/containers/[containerId]

1 file changed

+61
-33
lines changed

frontend/src/routes/(app)/containers/[containerId]/+page.svelte

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
} from '$lib/icons';
4242
import { parse as parseYaml } from 'yaml';
4343
import type { IncludeFile } from '$lib/types/project.type';
44+
import { projectService } from '$lib/services/project-service';
45+
import { environmentStore } from '$lib/stores/environment.store.svelte';
4446
let { data } = $props();
4547
let container = $derived(data?.container as ContainerDetailsDto);
4648
let stats = $state(null as ContainerStatsType | null);
@@ -150,34 +152,58 @@
150152
// Find which file (root compose or an include file) directly defines this service.
151153
// Returns { includeFile: null } for root compose, { includeFile: <file> } for a sub-file,
152154
// or null if the service isn't found anywhere (hides the tab).
153-
const serviceComposeSource = $derived(
154-
(() => {
155-
if (!project || !composeServiceName || !composeInfo) return null;
156-
157-
const hasService = (content: string): boolean => {
158-
try {
159-
const parsed = parseYaml(content) as Record<string, unknown> | null;
160-
return !!(parsed?.services && (parsed.services as Record<string, unknown>)[composeServiceName]);
161-
} catch {
162-
return false;
163-
}
164-
};
155+
//
156+
// Include file content is lazy-loaded (PR #2259), so we fetch on-demand via
157+
// getProjectFileForEnvironment, stopping as soon as the service is found.
158+
const hasServiceInContent = (content: string, serviceName: string): boolean => {
159+
try {
160+
const parsed = parseYaml(content) as Record<string, unknown> | null;
161+
return !!(parsed?.services && (parsed.services as Record<string, unknown>)[serviceName]);
162+
} catch {
163+
return false;
164+
}
165+
};
165166
166-
if (project.composeContent && hasService(project.composeContent)) {
167-
return { includeFile: null as IncludeFile | null };
168-
}
167+
async function resolveServiceComposeSource(
168+
proj: typeof project,
169+
svcName: string,
170+
info: typeof composeInfo
171+
): Promise<{ includeFile: IncludeFile | null } | null> {
172+
if (!proj || !svcName || !info) return null;
173+
174+
// Check root compose first (content is always present)
175+
if (proj.composeContent && hasServiceInContent(proj.composeContent, svcName)) {
176+
return { includeFile: null };
177+
}
178+
179+
// Lazy-fetch include file contents one at a time until we find the service
180+
const includes = proj.includeFiles ?? [];
181+
if (includes.length === 0) return null;
182+
183+
const envId = await environmentStore.getCurrentEnvironmentId().catch(() => null);
184+
if (!envId) return null;
169185
170-
for (const f of project.includeFiles ?? []) {
171-
if (hasService(f.content)) {
172-
return { includeFile: f };
186+
for (const f of includes) {
187+
if (f.content && hasServiceInContent(f.content, svcName)) {
188+
return { includeFile: f };
189+
}
190+
try {
191+
const loaded = await projectService.getProjectFileForEnvironment(envId, proj.id, f.relativePath);
192+
if (loaded?.content && hasServiceInContent(loaded.content, svcName)) {
193+
return { includeFile: { ...f, content: loaded.content } };
173194
}
195+
} catch {
196+
// Skip files that fail to load
174197
}
198+
}
199+
return null;
200+
}
175201
176-
return null;
177-
})()
202+
const serviceComposeSourcePromise = $derived(
203+
resolveServiceComposeSource(project, composeServiceName, composeInfo)
178204
);
179205
180-
const showComposeTab = $derived(!!composeInfo && !!serviceComposeSource);
206+
const showComposeTab = $derived(!!composeInfo && !!project);
181207
182208
const tabItems = $derived<TabItem[]>([
183209
{ value: 'overview', label: m.common_overview(), icon: ContainersIcon },
@@ -335,18 +361,20 @@
335361
</Tabs.Content>
336362
{/if}
337363

338-
{#if project && serviceComposeSource}
339-
<Tabs.Content value="compose" class="h-full min-h-0">
340-
{#key `${project?.id}-${serviceComposeSource?.includeFile?.relativePath ?? 'root'}`}
341-
<ContainerComposePanel
342-
{project}
343-
serviceName={composeServiceName}
344-
includeFile={serviceComposeSource.includeFile}
345-
rootFilename={rootComposeFilename}
346-
/>
347-
{/key}
348-
</Tabs.Content>
349-
{/if}
364+
{#await serviceComposeSourcePromise then serviceComposeSource}
365+
{#if project && serviceComposeSource}
366+
<Tabs.Content value="compose" class="h-full min-h-0">
367+
{#key `${project?.id}-${serviceComposeSource?.includeFile?.relativePath ?? 'root'}`}
368+
<ContainerComposePanel
369+
{project}
370+
serviceName={composeServiceName}
371+
includeFile={serviceComposeSource.includeFile}
372+
rootFilename={rootComposeFilename}
373+
/>
374+
{/key}
375+
</Tabs.Content>
376+
{/if}
377+
{/await}
350378
{/snippet}
351379
</TabbedPageLayout>
352380
{:else}

0 commit comments

Comments
 (0)