+
+ {diagnosis.hops.map((hop) => (
+ -
+
+ {HOP_MARK[hop.result]}
+
+ {hop.label}
+ {HOP_NOTE[hop.result]}
+
+ ))}
+
+
+
What to try
+
+ {diagnosis.steps.map((step) => (
+ - {step}
+ ))}
+
+
+
+ {diagnosis.facts.map(([label, value]) => (
+
+
- {label}
+ - {value}
+
+ ))}
+
+
+
+
+ {onAddRoute ? (
+
+ ) : null}
+
+
+ );
+}
diff --git a/apps/web/src/components/settings/ConnectionsSettings.tsx b/apps/web/src/components/settings/ConnectionsSettings.tsx
index a3759c0dbeb48..5585b138dc349 100644
--- a/apps/web/src/components/settings/ConnectionsSettings.tsx
+++ b/apps/web/src/components/settings/ConnectionsSettings.tsx
@@ -85,6 +85,11 @@ import { LocalEnvironmentSetting } from "./LocalEnvironmentSetting";
import { searchableSetting } from "./settingsSearch";
import { EnvironmentIconMenu } from "./EnvironmentIconPicker";
import { EnvironmentRoutesList } from "./EnvironmentRoutesList";
+import {
+ ConnectionDiagnosisPanel,
+ ConnectionDiagnosisSummary,
+ useConnectionDiagnosis,
+} from "./ConnectionDiagnosisDetails";
import { usePreparedConnection } from "~/state/session";
import {
EnvironmentRow,
@@ -1468,6 +1473,7 @@ function SavedBackendListRow({
}: SavedBackendListRowProps) {
const [routesOpen, setRoutesOpen] = useState(false);
const [permissionsOpen, setPermissionsOpen] = useState(false);
+ const [diagnosisOpen, setDiagnosisOpen] = useState(false);
const environmentId = environment.environmentId;
const unsupported = environment.connection.phase === "unsupported";
const blocked = unsupported && !hasSavedBearerRoute(environment.entry);
@@ -1541,6 +1547,13 @@ function SavedBackendListRow({
) {
setLastRelayHttpBaseUrl(discoveredRelayHttpBaseUrl);
}
+ const retryNow = useAtomCommand(environmentCatalog.retryNow);
+ const {
+ diagnosis: currentDiagnosis,
+ state: connectionState,
+ relayOnly,
+ } = useConnectionDiagnosis(environment);
+ const diagnosis = resumingServerUpdate ? null : currentDiagnosis;
const prepared = usePreparedConnection(environmentId);
const connectedTarget = isConnected && prepared._tag === "Some" ? prepared.value.target : null;
const mcpUrl = environmentMcpUrl({
@@ -1555,7 +1568,8 @@ function SavedBackendListRow({
const routeCount = connectionRoutes(environment.entry).length;
const subtitleText = [
environmentTransportLabel(environment, connectedTarget),
- resumingServerUpdate ? "Restarting" : status.text,
+ // The headline below names the failure in full.
+ diagnosis ? null : resumingServerUpdate ? "Restarting" : status.text,
enabled && versionMismatch ? serverVersion : null,
]
.filter((value): value is string => value !== null)
@@ -1598,6 +1612,7 @@ function SavedBackendListRow({
enabled &&
status.tone === "error" &&
!resumingServerUpdate &&
+ !diagnosis &&
"text-destructive",
)}
/>
@@ -1649,14 +1664,33 @@ function SavedBackendListRow({
}
below={
- serverUpdateState.status !== "idle" ? (
-