fix: restore Docker health indicator and fix empty expand when hints unavailable

This commit is contained in:
2026-04-04 20:51:11 +03:00
parent 97d2980e95
commit 27ec23921d
+77 -11
View File
@@ -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<DockerHealth | null>(null);
let healthChecked = $state(false);
let healthInterval: ReturnType<typeof setInterval> | 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);
});
</script>
@@ -171,6 +197,46 @@
<!-- Footer controls -->
<div class="space-y-3 border-t border-[var(--border-primary)] px-4 py-3">
{#if healthChecked}
<div class="rounded-md {dockerConnected ? '' : 'bg-red-50 dark:bg-red-950/30'}">
<button
type="button"
class="flex w-full items-center gap-2 px-2 py-1.5 text-xs {dockerConnected ? 'text-emerald-600' : 'text-red-500 cursor-pointer'}"
onclick={() => { if (!dockerConnected) hintsExpanded = !hintsExpanded; }}
disabled={dockerConnected}
>
<span class="relative flex h-2 w-2">
{#if dockerConnected}
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-50"></span>
{/if}
<span class="relative inline-flex h-2 w-2 rounded-full {dockerConnected ? 'bg-emerald-500' : 'bg-red-500'}"></span>
</span>
<span class="flex-1 text-left">Docker {dockerConnected ? $t('health.connected') : $t('health.disconnected')}</span>
{#if !dockerConnected && dockerHealth?.error}
<svg class="h-3 w-3 transition-transform {hintsExpanded ? 'rotate-180' : ''}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
{/if}
</button>
{#if !dockerConnected && hintsExpanded && dockerHealth?.error}
<div class="px-2 pb-2">
<code class="block text-[11px] text-red-600 dark:text-red-400 break-all leading-relaxed">{dockerHealth.error}</code>
<button
type="button"
class="mt-2 w-full rounded border border-red-300 dark:border-red-700 px-2 py-1 text-[11px] font-medium text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors"
onclick={async () => {
try {
const h = await getHealth();
dockerHealth = h.docker;
} catch {
dockerHealth = { connected: false };
}
}}
>
{$t('health.retryNow')}
</button>
</div>
{/if}
</div>
{/if}
<div class="flex items-center justify-between">
<ThemeToggle />
<LocaleSwitcher />