This commit is contained in:
Jarek Krochmalski committed 2026-01-11 07:16:18 +01:00
1 parent bd7b832394
commit 7643807717
86 files changed
+3644 -1385

No files matched your search

@@ -285,11 +285,19 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
const inspectUrl = `${config.type}://${config.host}:${config.port}${inspectPath}`;
const inspectHeaders: Record<string, string> = {};
if (config.hawserToken) inspectHeaders['X-Hawser-Token'] = config.hawserToken;
inspectResponse = await fetch(inspectUrl, {
headers: inspectHeaders,
// @ts-ignore
tls: config.type === 'https' ? { ca: config.ca, cert: config.cert, key: config.key } : undefined
});
const fetchOpts: any = { headers: inspectHeaders };
if (config.type === 'https') {
fetchOpts.tls = {
sessionTimeout: 0, // Disable TLS session caching for mTLS
servername: config.host,
rejectUnauthorized: true
};
if (config.ca) fetchOpts.tls.ca = [config.ca];
if (config.cert) fetchOpts.tls.cert = [config.cert];
if (config.key) fetchOpts.tls.key = config.key;
fetchOpts.keepalive = false;
}
inspectResponse = await fetch(inspectUrl, fetchOpts);
}
if (inspectResponse.ok) {
@@ -341,12 +349,22 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
const logsUrl = `${config.type}://${config.host}:${config.port}${logsPath}`;
const logsHeaders: Record<string, string> = {};
if (config.hawserToken) logsHeaders['X-Hawser-Token'] = config.hawserToken;
response = await fetch(logsUrl, {
const fetchOpts: any = {
headers: logsHeaders,
signal: abortController?.signal,
// @ts-ignore
tls: config.type === 'https' ? { ca: config.ca, cert: config.cert, key: config.key } : undefined
});
signal: abortController?.signal
};
if (config.type === 'https') {
fetchOpts.tls = {
sessionTimeout: 0, // Disable TLS session caching for mTLS
servername: config.host,
rejectUnauthorized: true
};
if (config.ca) fetchOpts.tls.ca = [config.ca];
if (config.cert) fetchOpts.tls.cert = [config.cert];
if (config.key) fetchOpts.tls.key = config.key;
fetchOpts.keepalive = false;
}
response = await fetch(logsUrl, fetchOpts);
}
if (!response.ok) {
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getContainerStats } from '$lib/server/docker';
import { getContainerStats, EnvironmentNotFoundError } from '$lib/server/docker';
import { authorize } from '$lib/server/authorize';
import { hasEnvironments } from '$lib/server/db';
@@ -47,6 +47,28 @@ function calculateBlockIO(stats: any): { read: number; write: number } {
return { read, write };
}
/**
* Calculate memory usage the same way Docker CLI does.
* Docker subtracts cache (inactive_file) from total usage to show actual memory consumption.
* - cgroup v2: subtract inactive_file from stats
* - cgroup v1: subtract total_inactive_file from stats
* See: https://docs.docker.com/engine/containers/runmetrics/
*
* Returns: { usage: actual memory (minus cache), raw: total usage, cache: file cache }
*/
function calculateMemoryUsage(memoryStats: any): { usage: number; raw: number; cache: number } {
const raw = memoryStats?.usage || 0;
const stats = memoryStats?.stats || {};
// cgroup v2 uses 'inactive_file', cgroup v1 uses 'total_inactive_file'
const cache = stats.inactive_file ?? stats.total_inactive_file ?? 0;
// Only subtract cache if it's less than raw usage (sanity check)
const usage = (cache > 0 && cache < raw) ? raw - cache : raw;
return { usage, raw, cache };
}
export const GET: RequestHandler = async ({ params, url, cookies }) => {
const auth = await authorize(cookies);
@@ -67,15 +89,17 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
const stats = await getContainerStats(params.id, envIdNum) as any;
const cpuPercent = calculateCpuPercent(stats);
const memoryUsage = stats.memory_stats?.usage || 0;
const memory = calculateMemoryUsage(stats.memory_stats);
const memoryLimit = stats.memory_stats?.limit || 1;
const memoryPercent = (memoryUsage / memoryLimit) * 100;
const memoryPercent = (memory.usage / memoryLimit) * 100;
const networkIO = calculateNetworkIO(stats);
const blockIO = calculateBlockIO(stats);
return json({
cpuPercent: Math.round(cpuPercent * 100) / 100,
memoryUsage,
memoryUsage: memory.usage,
memoryRaw: memory.raw,
memoryCache: memory.cache,
memoryLimit,
memoryPercent: Math.round(memoryPercent * 100) / 100,
networkRx: networkIO.rx,
@@ -85,6 +109,10 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
timestamp: Date.now()
});
} catch (error: any) {
// Return 404 for deleted environments so client can clear stale cache
if (error instanceof EnvironmentNotFoundError) {
return json({ error: 'Environment not found' }, { status: 404 });
}
console.error('Failed to get container stats:', error);
return json({ error: error.message || 'Failed to get stats' }, { status: 500 });
}
+33 -5
View File
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { listContainers, getContainerStats } from '$lib/server/docker';
import { listContainers, getContainerStats, EnvironmentNotFoundError } from '$lib/server/docker';
import { authorize } from '$lib/server/authorize';
import { hasEnvironments } from '$lib/server/db';
import type { ContainerStats } from '$lib/types';
@@ -48,6 +48,28 @@ function calculateBlockIO(stats: any): { read: number; write: number } {
return { read, write };
}
/**
* Calculate memory usage the same way Docker CLI does.
* Docker subtracts cache (inactive_file) from total usage to show actual memory consumption.
* - cgroup v2: subtract inactive_file from stats
* - cgroup v1: subtract total_inactive_file from stats
* See: https://docs.docker.com/engine/containers/runmetrics/
*
* Returns: { usage: actual memory (minus cache), raw: total usage, cache: file cache }
*/
function calculateMemoryUsage(memoryStats: any): { usage: number; raw: number; cache: number } {
const raw = memoryStats?.usage || 0;
const stats = memoryStats?.stats || {};
// cgroup v2 uses 'inactive_file', cgroup v1 uses 'total_inactive_file'
const cache = stats.inactive_file ?? stats.total_inactive_file ?? 0;
// Only subtract cache if it's less than raw usage (sanity check)
const usage = (cache > 0 && cache < raw) ? raw - cache : raw;
return { usage, raw, cache };
}
// Helper to add timeout to promises
function withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {
return Promise.race([
@@ -112,10 +134,10 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
if (!stats) return null;
const cpuPercent = calculateCpuPercent(stats);
// Use raw memory usage (total memory attributed to container)
const memoryUsage = stats.memory_stats?.usage || 0;
// Calculate memory usage the same way Docker CLI does (excludes cache)
const memory = calculateMemoryUsage(stats.memory_stats);
const memoryLimit = stats.memory_stats?.limit || 1;
const memoryPercent = (memoryUsage / memoryLimit) * 100;
const memoryPercent = (memory.usage / memoryLimit) * 100;
const networkIO = calculateNetworkIO(stats);
const blockIO = calculateBlockIO(stats);
@@ -123,7 +145,9 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
id: container.id,
name: container.name,
cpuPercent: Math.round(cpuPercent * 100) / 100,
memoryUsage,
memoryUsage: memory.usage,
memoryRaw: memory.raw,
memoryCache: memory.cache,
memoryLimit,
memoryPercent: Math.round(memoryPercent * 100) / 100,
networkRx: networkIO.rx,
@@ -142,6 +166,10 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
return json(validStats);
} catch (error: any) {
// Return 404 for deleted environments so client can clear stale cache
if (error instanceof EnvironmentNotFoundError) {
return json({ error: 'Environment not found' }, { status: 404 });
}
console.error('Failed to get container stats:', error);
return json([], { status: 200 }); // Return empty array instead of error
}