This commit is contained in:
jarek committed 2026-03-13 08:22:46 +01:00
1 parent 790ce092ee
commit 55f3101a19
51 files changed
+763 -599

No files matched your search

+10 -4
View File
@@ -30,8 +30,11 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
const details = await inspectContainer(params.id, envIdNum);
return json(details);
} catch (error) {
console.error('Error inspecting container:', error);
} catch (error: any) {
if (error?.statusCode === 404) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error inspecting container:', error?.message || error);
return json({ error: 'Failed to inspect container' }, { status: 500 });
}
};
@@ -96,8 +99,11 @@ export const DELETE: RequestHandler = async (event) => {
}
return json({ success: true });
} catch (error) {
console.error('Error removing container:', error);
} catch (error: any) {
if (error?.statusCode === 404) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error removing container:', error?.message || error);
return json({ error: 'Failed to remove container' }, { status: 500 });
}
};
@@ -26,7 +26,10 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
return json(result);
} catch (error: any) {
console.error('Error listing container directory:', error);
if (error?.statusCode === 404) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error listing container directory:', error?.message || error);
return json({ error: error.message || 'Failed to list directory' }, { status: 500 });
}
};
@@ -36,7 +36,10 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
return json({ content, path });
} catch (error: any) {
console.error('Error reading container file:', error);
if (error?.statusCode === 404 && !error.message?.includes('No such file')) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error reading container file:', error?.message || error);
const msg = error.message || String(error);
if (msg.includes('No such file or directory')) {
@@ -92,7 +95,7 @@ export const PUT: RequestHandler = async ({ params, url, cookies, request }) =>
return json({ success: true, path });
} catch (error: any) {
console.error('Error writing container file:', error);
console.error('Error writing container file:', error?.message || error);
const msg = error.message || String(error);
if (msg.includes('Permission denied')) {
@@ -76,7 +76,7 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
return new Response(body, { headers });
} catch (error: any) {
console.error('Error downloading container file:', error);
console.error('Error downloading container file:', error?.message || error);
if (error.message?.includes('No such file or directory')) {
return new Response(JSON.stringify({ error: 'File not found' }), {
@@ -140,7 +140,7 @@ export const POST: RequestHandler = async ({ params, url, request, cookies }) =>
errors: errors.length > 0 ? errors : undefined
});
} catch (error: any) {
console.error('Error uploading to container:', error);
console.error('Error uploading to container:', error?.message || error);
if (error.message?.includes('Permission denied')) {
return json({ error: 'Permission denied to write to this path' }, { status: 403 });
@@ -46,8 +46,11 @@ export const POST: RequestHandler = async (event) => {
}
return json({ success: true });
} catch (error) {
console.error('Error renaming container:', error);
} catch (error: any) {
if (error?.statusCode === 404) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error renaming container:', error?.message || error);
return json({ error: 'Failed to rename container' }, { status: 500 });
}
};
@@ -38,8 +38,11 @@ export const POST: RequestHandler = async (event) => {
await auditContainer(event, 'restart', params.id, containerName, envIdNum);
return json({ success: true });
} catch (error) {
console.error('Error restarting container:', error);
} catch (error: any) {
if (error?.statusCode === 404) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error restarting container:', error?.message || error);
return json({ error: 'Failed to restart container' }, { status: 500 });
}
};
@@ -31,8 +31,11 @@ export const POST: RequestHandler = async (event) => {
await auditContainer(event, 'start', params.id, containerName, envIdNum);
return json({ success: true });
} catch (error) {
console.error('Error starting container:', error);
} catch (error: any) {
if (error?.statusCode === 404) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error starting container:', error?.message || error);
return json({ error: 'Failed to start container' }, { status: 500 });
}
};
@@ -31,8 +31,11 @@ export const POST: RequestHandler = async (event) => {
await auditContainer(event, 'stop', params.id, containerName, envIdNum);
return json({ success: true });
} catch (error) {
console.error('Error stopping container:', error);
} catch (error: any) {
if (error?.statusCode === 404) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error stopping container:', error?.message || error);
return json({ error: 'Failed to stop container' }, { status: 500 });
}
};
@@ -47,8 +47,11 @@ export const POST: RequestHandler = async (event) => {
await auditContainer(event, 'update', container.id, options.name, envIdNum, { ...options, startAfterUpdate });
return json({ success: true, id: container.id });
} catch (error) {
console.error('Error updating container:', error);
return json({ error: 'Failed to update container', details: String(error) }, { status: 500 });
} catch (error: any) {
if (error?.statusCode === 404) {
return json({ error: error.json?.message || 'Container not found' }, { status: 404 });
}
console.error('Error updating container:', error?.message || error);
return json({ error: 'Failed to update container', details: error?.message || String(error) }, { status: 500 });
}
};
@@ -4,6 +4,7 @@ import { authorize } from '$lib/server/authorize';
import { listContainers, inspectContainer, checkImageUpdateAvailable } from '$lib/server/docker';
import { clearPendingContainerUpdates, addPendingContainerUpdate } from '$lib/server/db';
import { isSystemContainer } from '$lib/server/scheduler/tasks/update-utils';
import { createJobResponse } from '$lib/server/sse';
export interface UpdateCheckResult {
containerId: string;
@@ -19,9 +20,9 @@ export interface UpdateCheckResult {
/**
* Check all containers for available image updates.
* Returns all results at once after checking in parallel.
* Returns progress events during checking, final result when done.
*/
export const POST: RequestHandler = async ({ url, cookies }) => {
export const POST: RequestHandler = async ({ url, cookies, request }) => {
const auth = await authorize(cookies);
const envId = url.searchParams.get('env');
@@ -32,21 +33,21 @@ export const POST: RequestHandler = async ({ url, cookies }) => {
return json({ error: 'Permission denied' }, { status: 403 });
}
try {
return createJobResponse(async (send) => {
// Clear existing pending updates for this environment before checking
if (envIdNum) {
await clearPendingContainerUpdates(envIdNum);
}
const allContainers = await listContainers(true, envIdNum);
// Include all containers (system containers get flagged, not filtered)
const containers = allContainers;
send('progress', { checked: 0, total: containers.length });
// Check container for updates
let checked = 0;
const checkContainer = async (container: typeof containers[0]): Promise<UpdateCheckResult> => {
try {
// Get container's image name from config
const inspectData = await inspectContainer(container.id, envIdNum) as any;
const imageName = inspectData.Config?.Image;
const currentImageId = inspectData.Image;
@@ -62,7 +63,6 @@ export const POST: RequestHandler = async ({ url, cookies }) => {
};
}
// Use shared update detection function
const result = await checkImageUpdateAvailable(imageName, currentImageId, envIdNum);
return {
@@ -88,13 +88,23 @@ export const POST: RequestHandler = async ({ url, cookies }) => {
}
};
// Check all containers in parallel
const results = await Promise.all(containers.map(checkContainer));
// Sliding window concurrency limit to avoid DNS threadpool saturation (#676).
const CONCURRENCY = 20;
const results: UpdateCheckResult[] = new Array(containers.length);
let next = 0;
async function runNext(): Promise<void> {
while (next < containers.length) {
const idx = next++;
results[idx] = await checkContainer(containers[idx]);
checked++;
send('progress', { checked, total: containers.length });
}
}
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, containers.length) }, () => runNext()));
const updatesFound = results.filter(r => r.hasUpdate).length;
// Save containers with updates to the database for persistence
// Skip system containers (Dockhand/Hawser) - they use their own update paths
if (envIdNum) {
for (const result of results) {
if (result.hasUpdate && !result.systemContainer) {
@@ -108,13 +118,10 @@ export const POST: RequestHandler = async ({ url, cookies }) => {
}
}
return json({
send('result', {
total: containers.length,
updatesFound,
results
});
} catch (error: any) {
console.error('Error checking for updates:', error);
return json({ error: 'Failed to check for updates', details: error.message }, { status: 500 });
}
}, request);
};