v1.0.19
This commit is contained in:
1 parent
4b430340db
commit
9c451aedf9
120 files changed
+7777
-6046
No files matched your search
@@ -3,6 +3,7 @@ import {
|
||||
getEnvironments,
|
||||
getLatestHostMetrics,
|
||||
getHostMetrics,
|
||||
getMetricsCollectionInterval,
|
||||
getContainerEventStats,
|
||||
getContainerEvents,
|
||||
getEnvSetting,
|
||||
@@ -14,13 +15,17 @@ import {
|
||||
listImages,
|
||||
listNetworks,
|
||||
getContainerStats,
|
||||
getDiskUsage
|
||||
getDiskUsage,
|
||||
dockerPing,
|
||||
DockerConnectionError
|
||||
} from '$lib/server/docker';
|
||||
import { listComposeStacks } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { prefersJSON, sseToJSON } from '$lib/server/sse';
|
||||
import type { EnvironmentStats } from '../+server';
|
||||
import { parseLabels } from '$lib/utils/label-colors';
|
||||
|
||||
|
||||
// Skip disk usage collection (Synology NAS performance fix)
|
||||
const SKIP_DF_COLLECTION = process.env.SKIP_DF_COLLECTION === 'true' || process.env.SKIP_DF_COLLECTION === '1';
|
||||
|
||||
@@ -68,6 +73,9 @@ setInterval(() => {
|
||||
}
|
||||
}, 10 * 60 * 1000); // Every 10 minutes
|
||||
|
||||
// Register cache reporter for memory monitoring
|
||||
|
||||
|
||||
async function getCachedDiskUsage(envId: number): Promise<any> {
|
||||
const cached = diskUsageCache.get(envId);
|
||||
const now = Date.now();
|
||||
@@ -125,10 +133,14 @@ function calculateMemoryUsage(memoryStats: any): number {
|
||||
return usage;
|
||||
}
|
||||
|
||||
// Target time window for metrics history charts (15 minutes)
|
||||
const METRICS_HISTORY_WINDOW_MS = 15 * 60 * 1000;
|
||||
|
||||
// Progressive stats loading - returns stats object and emits partial updates via callback
|
||||
async function getEnvironmentStatsProgressive(
|
||||
env: any,
|
||||
onPartialUpdate: (stats: Partial<EnvironmentStats> & { id: number }) => void
|
||||
onPartialUpdate: (stats: Partial<EnvironmentStats> & { id: number }) => void,
|
||||
metricsPointCount: number
|
||||
): Promise<EnvironmentStats> {
|
||||
const envStats: EnvironmentStats = {
|
||||
id: env.id,
|
||||
@@ -187,7 +199,7 @@ async function getEnvironmentStatsProgressive(
|
||||
getLatestHostMetrics(env.id),
|
||||
getContainerEventStats(env.id),
|
||||
getContainerEvents({ environmentId: env.id, limit: 10 }),
|
||||
getHostMetrics(30, env.id),
|
||||
getHostMetrics(metricsPointCount, env.id),
|
||||
getPendingContainerUpdates(env.id)
|
||||
]);
|
||||
|
||||
@@ -237,6 +249,20 @@ async function getEnvironmentStatsProgressive(
|
||||
loading: { ...envStats.loading }
|
||||
});
|
||||
|
||||
// Quick reachability check — if ping fails, skip all expensive Docker API calls
|
||||
if (!await dockerPing(env.id)) {
|
||||
envStats.online = false;
|
||||
envStats.error = 'Environment offline';
|
||||
envStats.loading = undefined;
|
||||
onPartialUpdate({
|
||||
id: env.id,
|
||||
online: false,
|
||||
error: 'Environment offline',
|
||||
loading: undefined
|
||||
});
|
||||
return envStats;
|
||||
}
|
||||
|
||||
// Helper to get valid size
|
||||
const getValidSize = (size: number | undefined | null): number => {
|
||||
return size && size > 0 ? size : 0;
|
||||
@@ -511,7 +537,7 @@ async function getEnvironmentStatsProgressive(
|
||||
return envStats;
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies }) => {
|
||||
export const GET: RequestHandler = async ({ request, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !await auth.can('environments', 'view')) {
|
||||
return new Response(JSON.stringify({ error: 'Permission denied' }), {
|
||||
@@ -535,6 +561,7 @@ export const GET: RequestHandler = async ({ cookies }) => {
|
||||
let controllerClosed = false;
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Safe enqueue that checks if controller is still open
|
||||
@@ -573,17 +600,23 @@ export const GET: RequestHandler = async ({ cookies }) => {
|
||||
}));
|
||||
safeEnqueue(`event: environments\ndata: ${JSON.stringify(envList)}\n\n`);
|
||||
|
||||
// Calculate metrics point count based on configured interval
|
||||
const metricsIntervalMs = await getMetricsCollectionInterval();
|
||||
const metricsPointCount = Math.ceil(METRICS_HISTORY_WINDOW_MS / metricsIntervalMs);
|
||||
|
||||
// Fetch stats for each environment with progressive updates
|
||||
const promises = environments.map(async (env) => {
|
||||
try {
|
||||
await getEnvironmentStatsProgressive(env, (partialStats) => {
|
||||
// Send partial update as it arrives
|
||||
safeEnqueue(`event: partial\ndata: ${JSON.stringify(partialStats)}\n\n`);
|
||||
});
|
||||
}, metricsPointCount);
|
||||
// Send final complete stats event for this environment
|
||||
safeEnqueue(`event: complete\ndata: ${JSON.stringify({ id: env.id })}\n\n`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to get stats for ${env.name}:`, error);
|
||||
if (!(error instanceof DockerConnectionError)) {
|
||||
console.error(`Failed to get stats for ${env.name}:`, error);
|
||||
}
|
||||
// Convert technical error to user-friendly message
|
||||
const errorStr = String(error);
|
||||
let friendlyError = 'Connection error';
|
||||
@@ -611,19 +644,25 @@ export const GET: RequestHandler = async ({ cookies }) => {
|
||||
} catch {
|
||||
// Already closed
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
// Called when the client disconnects
|
||||
controllerClosed = true;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
const sseResponse = new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
'Connection': 'keep-alive',
|
||||
'X-Accel-Buffering': 'no'
|
||||
}
|
||||
});
|
||||
|
||||
if (prefersJSON(request)) return sseToJSON(sseResponse);
|
||||
return sseResponse;
|
||||
};
|
||||
Reference in new issue
Block a user