fix: resolve ERR_INSUFFICIENT_RESOURCES connection exhaustion

- Add concurrency limiter (max 4 GET requests) to API layer, leaving
  slots for SSE and health checks. Write ops bypass the limiter.
- Add AbortController to ContainerStats, project detail page, and
  dashboard to cancel in-flight requests on navigation/unmount.
- Move global SSE connection from layout to events page (only consumer).
- Add 30s heartbeat to SSE endpoint to detect zombie connections.
- Serialize dashboard project fetches to avoid parallel burst.
- Rebuild frontend in dev-server.sh so go:embed stays in sync.
This commit is contained in:
2026-04-13 00:12:14 +03:00
parent 791cd4d6af
commit 96fd910603
7 changed files with 233 additions and 87 deletions
+2 -21
View File
@@ -8,12 +8,9 @@
import LocaleSwitcher from '$lib/components/LocaleSwitcher.svelte';
import { IconDashboard, IconProjects, IconDeploy, IconEvents, IconWifi, IconSettings, IconMenu, IconX, IconLogout, IconGlobe } from '$lib/components/icons';
import { goto } from '$app/navigation';
import { connectGlobalEvents, type SSEConnection } from '$lib/sse';
import { instanceStatusStore } from '$lib/stores/instance-status';
import { resolvedTheme, applyTheme } from '$lib/stores/theme';
import { exchangeOidcToken, setAuthToken, clearAuth, isAuthenticated } from '$lib/auth';
import { logout as apiLogout, getHealth } from '$lib/api';
import { publishEventLog } from '$lib/stores/event-log-bus';
import type { DockerHealth, ProxyHealth } from '$lib/types';
import { t } from '$lib/i18n';
@@ -38,7 +35,6 @@
return pathname.startsWith(href);
}
let sseConnection: SSEConnection | null = null;
let sidebarOpen = $state(false);
let dockerHealth = $state<DockerHealth | null>(null);
let proxyHealth = $state<ProxyHealth | null>(null);
@@ -85,26 +81,13 @@
}
});
// Start SSE and health polling when authenticated.
// Start health polling when authenticated.
// Uses $effect to react to route changes (e.g., after login navigation).
$effect(() => {
void $page.url.pathname;
if (!isAuthenticated() || sseConnection) return;
if (!isAuthenticated() || healthInterval) return;
sseConnection = connectGlobalEvents({
onInstanceStatus(payload) {
instanceStatusStore.update(payload);
},
onDeployStatus(payload) {
instanceStatusStore.notifyDeploy(payload);
},
onEventLog(payload) {
publishEventLog(payload);
}
});
// Poll Docker health every 30s.
async function checkHealth() {
try {
const h = await getHealth();
@@ -121,8 +104,6 @@
});
onDestroy(() => {
sseConnection?.close();
sseConnection = null;
if (healthInterval) clearInterval(healthInterval);
});
</script>