From 27ec23921d1a6ad0246918ef6928829f416f4eab Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sat, 4 Apr 2026 20:51:11 +0300 Subject: [PATCH] fix: restore Docker health indicator and fix empty expand when hints unavailable --- web/src/routes/+layout.svelte | 88 ++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 33f1d97..40ab99b 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -12,7 +12,8 @@ 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 } from '$lib/api'; + import { logout as apiLogout, getHealth } from '$lib/api'; + import type { DockerHealth } from '$lib/types'; import { t } from '$lib/i18n'; interface Props { @@ -35,6 +36,12 @@ let sseConnection: SSEConnection | null = null; let sidebarOpen = $state(false); + let dockerHealth = $state(null); + let healthChecked = $state(false); + let healthInterval: ReturnType | null = null; + let hintsExpanded = $state(false); + + const dockerConnected = $derived(dockerHealth?.connected ?? false); // Hide sidebar and chrome on the login page. const isLoginPage = $derived($page.url.pathname === '/login'); @@ -68,23 +75,42 @@ goto('/', { replaceState: true }); } } + }); - // Only connect SSE when authenticated (has a token). - if (isAuthenticated()) { - sseConnection = connectGlobalEvents({ - onInstanceStatus(payload) { - instanceStatusStore.update(payload); - }, - onDeployStatus(payload) { - instanceStatusStore.notifyDeploy(payload); - } - }); + // Start SSE and 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; + + sseConnection = connectGlobalEvents({ + onInstanceStatus(payload) { + instanceStatusStore.update(payload); + }, + onDeployStatus(payload) { + instanceStatusStore.notifyDeploy(payload); + } + }); + + // Poll Docker health every 30s. + async function checkHealth() { + try { + const h = await getHealth(); + dockerHealth = h.docker; + } catch { + dockerHealth = { connected: false }; + } + healthChecked = true; } + checkHealth(); + healthInterval = setInterval(checkHealth, 30_000); }); onDestroy(() => { sseConnection?.close(); sseConnection = null; + if (healthInterval) clearInterval(healthInterval); }); @@ -171,6 +197,46 @@
+ {#if healthChecked} +
+ + {#if !dockerConnected && hintsExpanded && dockerHealth?.error} +
+ {dockerHealth.error} + +
+ {/if} +
+ {/if}