Dashboard: surface fleet load errors instead of infinite skeletons (0.38.1)
The cockpit cards gated purely on `fleet.data`, so any failed /api/dashboard/fleet request (e.g. a stale backend returning 404, or a 500) left the new components stuck on skeletons forever. Render a clear error banner with the API message and a Retry button when the query errors with no data, so the actual cause is visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c830d28b65
commit
0b95d7d4a2
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
"""Single source of truth for the StackPilot release version."""
|
"""Single source of truth for the StackPilot release version."""
|
||||||
|
|
||||||
APP_VERSION = "0.38.0"
|
APP_VERSION = "0.38.1"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "stackpilot-frontend",
|
"name": "stackpilot-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.38.0",
|
"version": "0.38.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Clock, RefreshCw } from "lucide-react";
|
import { AlertTriangle, Clock, RefreshCw } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Card } from "@/components/ui";
|
import { Card } from "@/components/ui";
|
||||||
import { HostHeader } from "@/components/hosts/HostHeader";
|
import { HostHeader } from "@/components/hosts/HostHeader";
|
||||||
@@ -66,33 +66,51 @@ export function Dashboard() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ---- Needs attention ---- */}
|
{/* ---- Fleet cockpit (attention + KPIs + status + hosts) ---- */}
|
||||||
<AttentionStrip items={fleet.data?.attention} loading={fleet.isLoading} />
|
{fleet.isError && !fleet.data ? (
|
||||||
|
<div className="flex items-center justify-between gap-3 rounded-card border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/50 dark:bg-red-900/20 dark:text-red-300">
|
||||||
{/* ---- Fleet KPIs ---- */}
|
<span className="flex items-center gap-2">
|
||||||
{fleet.data ? (
|
<AlertTriangle className="h-5 w-5 shrink-0" />
|
||||||
<FleetKpiRow kpis={fleet.data.kpis} />
|
Couldn’t load fleet overview: {apiErrorMessage(fleet.error)}
|
||||||
) : (
|
</span>
|
||||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">
|
<button
|
||||||
{Array.from({ length: 6 }).map((_, i) => (
|
onClick={refreshFleet}
|
||||||
<div key={i} className="sp-skeleton h-24" />
|
className="shrink-0 rounded-pill border border-red-300 px-3 py-1 text-xs font-medium hover:bg-red-100 dark:border-red-800 dark:hover:bg-red-900/40"
|
||||||
))}
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Needs attention */}
|
||||||
|
<AttentionStrip items={fleet.data?.attention} loading={fleet.isLoading} />
|
||||||
|
|
||||||
{/* ---- Status + hosts ---- */}
|
{/* Fleet KPIs */}
|
||||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-[340px_1fr]">
|
{fleet.data ? (
|
||||||
{fleet.data ? (
|
<FleetKpiRow kpis={fleet.data.kpis} />
|
||||||
<StackStatusBar totals={fleet.data.status_totals} unhealthy={fleet.data.kpis.unhealthy} />
|
) : (
|
||||||
) : (
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">
|
||||||
<div className="sp-skeleton h-40" />
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
)}
|
<div key={i} className="sp-skeleton h-24" />
|
||||||
{fleet.data ? (
|
))}
|
||||||
<HostResourceTable hosts={fleet.data.hosts} />
|
</div>
|
||||||
) : (
|
)}
|
||||||
<div className="sp-skeleton h-40" />
|
|
||||||
)}
|
{/* Status + hosts */}
|
||||||
</div>
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-[340px_1fr]">
|
||||||
|
{fleet.data ? (
|
||||||
|
<StackStatusBar totals={fleet.data.status_totals} unhealthy={fleet.data.kpis.unhealthy} />
|
||||||
|
) : (
|
||||||
|
<div className="sp-skeleton h-40" />
|
||||||
|
)}
|
||||||
|
{fleet.data ? (
|
||||||
|
<HostResourceTable hosts={fleet.data.hosts} />
|
||||||
|
) : (
|
||||||
|
<div className="sp-skeleton h-40" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ---- Local host stacks ---- */}
|
{/* ---- Local host stacks ---- */}
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
Reference in New Issue
Block a user