feat: DWH connectivity probe at startup — modal alert within 5s if unreachable

Backend: GET /health/dwh (unauthenticated) calls tht db ping with a 5s
timeout. Frontend: checkDwhHealth() races a 5s timer against the fetch;
on failure a non-dismissable Dialog with Retry appears immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 13:59:11 +02:00
co-authored by Claude Opus 4.6
parent 83942c0b5c
commit 3b52c8c08c
7 changed files with 60 additions and 7 deletions
+38 -1
View File
@@ -20,7 +20,11 @@ import { toast } from "sonner";
import {
closeSession, getMe, listSessions, resumeSession, getSession,
renameSession, setSessionGroup, archiveSession, unarchiveSession, deleteSession, prewarmRuntime,
checkDwhHealth,
} from "../api/sessions";
import {
Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,
} from "../components/ui/dialog";
import type { Principal, SessionScope, SessionSummary } from "../api/types";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useMemo, useRef, useState } from "react";
@@ -89,6 +93,19 @@ export function AppShell() {
const [stopConfirm, setStopConfirm] = useState(false);
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
const [renameGroupTarget, setRenameGroupTarget] = useState<string | null>(null);
const [dwhDown, setDwhDown] = useState(false);
const [dwhChecking, setDwhChecking] = useState(true);
const [dwhCheckEpoch, setDwhCheckEpoch] = useState(0);
useEffect(() => {
let cancelled = false;
setDwhChecking(true);
checkDwhHealth().then((r) => {
if (cancelled) return;
setDwhDown(!r.ok);
setDwhChecking(false);
});
return () => { cancelled = true; };
}, [dwhCheckEpoch]);
const groups = useMemo(
() => [...new Set(sessions.map((s) => s.group).filter((g): g is string => !!g))].sort(),
@@ -759,6 +776,26 @@ export function AppShell() {
}}
/>
)}
{dwhDown && (
<Dialog open onOpenChange={() => {}}>
<DialogContent showCloseButton={false} className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Connection unavailable</DialogTitle>
<DialogDescription>
The database is unreachable. Check the VPN connection and try again.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
disabled={dwhChecking}
onClick={() => setDwhCheckEpoch((e) => e + 1)}
>
{dwhChecking ? "Checking…" : "Retry"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
</div>
);
}
@@ -770,7 +807,7 @@ function EmptyState() {
Thoth<span className="text-primary">II</span>
</span>
<p className="mt-4 max-w-md text-balance text-[0.95rem] leading-relaxed text-muted-foreground">
Build datamarts from your data warehouse through a guided,
Build datamarts from your database through a guided,
human-in-the-loop workflow. Type your question in the box below to begin
a session.
</p>