From d35ef5c7a4322285aa46e4065ba4cd78a36c6ab8 Mon Sep 17 00:00:00 2001 From: Matthew Valancy Date: Tue, 16 Jun 2026 08:47:58 -0700 Subject: [PATCH] D1-first: stop surfacing Neo4j errors/labels + fix Admin /config crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the SPA backend-agnostic so it never shows Neo4j-specific labels/errors or crashes when Neo4j is not the backend (e.g. cloud D1). - useHealthCheck: make services.neo4j and services.mcp optional; base criticalServices on graphql only; guard mcp?/neo4j? status with optional chaining. A missing graph store no longer forces unhealthy/degraded. - useHealthStatus: make services.neo4j/mcp optional; base the unhealthy polling computation on graphql health only (missing neo4j != unhealthy). - Backend page: genericize the hard-coded "Neo4j Graph Database" service row, descriptions, architecture-diagram label, and summary card to provider-neutral wording ("Graph store" / "Graph database"). - Admin page: FIX REAL CRASH — the cloud /config response has no services block, so config.services.* threw a TypeError and white-screened /admin. Harden every access to config?.services?.X?.Y ?? fallback, also in the useSystemConfig getters and interface. Rename the "Neo4j" SVG node and traffic-flow edge/labels to a generic "Graph DB". - InteractiveGraphVisualization: genericize the two error-mapper strings ("ensure Neo4j is running" / "Verify Neo4j database is running") to "ensure/verify the graph backend is reachable". Workspace.tsx left untouched per request (edited separately). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../InteractiveGraphVisualization.tsx | 4 +- packages/web/src/hooks/useHealthCheck.ts | 16 ++- packages/web/src/hooks/useHealthStatus.ts | 9 +- packages/web/src/hooks/useSystemConfig.ts | 103 +++++++++--------- packages/web/src/pages/Admin.tsx | 18 +-- packages/web/src/pages/Backend.tsx | 30 ++--- 6 files changed, 94 insertions(+), 86 deletions(-) diff --git a/packages/web/src/components/InteractiveGraphVisualization.tsx b/packages/web/src/components/InteractiveGraphVisualization.tsx index c18e7236..77bb5bfa 100644 --- a/packages/web/src/components/InteractiveGraphVisualization.tsx +++ b/packages/web/src/components/InteractiveGraphVisualization.tsx @@ -4463,7 +4463,7 @@ export function InteractiveGraphVisualization({ onResetLayout, onNodeSelected }: } if (message.includes('Neo4j')) { - return "Database connection failed. Please ensure Neo4j is running and properly configured."; + return "Database connection failed. Please ensure the graph backend is reachable and properly configured."; } if (message.includes('Enum') && message.includes('cannot represent')) { @@ -4506,7 +4506,7 @@ export function InteractiveGraphVisualization({ onResetLayout, onNodeSelected }:
💡 Quick fixes:
• Run ./start to start the server
• Check if port 4127 is available
-
• Verify Neo4j database is running
+
• Verify the graph backend is reachable
)} diff --git a/packages/web/src/hooks/useHealthCheck.ts b/packages/web/src/hooks/useHealthCheck.ts index c64ff94d..fb51928b 100644 --- a/packages/web/src/hooks/useHealthCheck.ts +++ b/packages/web/src/hooks/useHealthCheck.ts @@ -19,8 +19,8 @@ export interface HealthCheckResult { timestamp: string; services: { graphql: ServiceHealth & { port: number }; - neo4j: ServiceHealth & { uri: string }; - mcp: McpHealth; + neo4j?: ServiceHealth & { uri: string }; + mcp?: McpHealth; }; } @@ -158,11 +158,15 @@ export function useHealthCheck(options: UseHealthCheckOptions = {}) { const getOverallStatus = useCallback((): 'healthy' | 'degraded' | 'unhealthy' | 'unknown' => { if (!health) return 'unknown'; - const criticalServices = [health.services.graphql, health.services.neo4j]; - const hasCriticalIssue = criticalServices.some(s => s.status === 'unhealthy'); - + const criticalServices = [health.services.graphql]; + const hasCriticalIssue = criticalServices.some(s => s?.status === 'unhealthy'); + if (hasCriticalIssue) return 'unhealthy'; - if (health.status === 'degraded' || health.services.mcp.status === 'unhealthy') return 'degraded'; + if ( + health.status === 'degraded' || + health.services.mcp?.status === 'unhealthy' || + health.services.neo4j?.status === 'unhealthy' + ) return 'degraded'; return 'healthy'; }, [health]); diff --git a/packages/web/src/hooks/useHealthStatus.ts b/packages/web/src/hooks/useHealthStatus.ts index c22fda8c..0f2cc55a 100644 --- a/packages/web/src/hooks/useHealthStatus.ts +++ b/packages/web/src/hooks/useHealthStatus.ts @@ -5,8 +5,8 @@ interface HealthStatus { timestamp: string; services: { graphql: { status: string; port: number }; - neo4j: { status: string; uri: string; error?: string }; - mcp: { status: string; port: number; error?: string }; + neo4j?: { status: string; uri: string; error?: string }; + mcp?: { status: string; port: number; error?: string }; }; } @@ -39,8 +39,7 @@ export function useHealthStatus() { const getPollingInterval = () => { if (!health) return 5000; // Initial check every 5 seconds - const hasUnhealthyService = health.services?.neo4j?.status !== 'healthy' || - health.services?.graphql?.status !== 'healthy'; + const hasUnhealthyService = health.services?.graphql?.status !== 'healthy'; // Poll every 5 seconds if unhealthy, 15 seconds if healthy return hasUnhealthyService ? 5000 : 15000; @@ -63,7 +62,7 @@ export function useHealthStatus() { return () => { if (timeoutId) clearTimeout(timeoutId); }; - }, [health?.services?.neo4j?.status, health?.services?.graphql?.status]); + }, [health?.services?.graphql?.status]); return { health, loading, error, refetch: checkHealth }; } \ No newline at end of file diff --git a/packages/web/src/hooks/useSystemConfig.ts b/packages/web/src/hooks/useSystemConfig.ts index d99fb3e9..84a054a7 100644 --- a/packages/web/src/hooks/useSystemConfig.ts +++ b/packages/web/src/hooks/useSystemConfig.ts @@ -1,53 +1,53 @@ import { useState, useEffect, useRef } from 'react'; interface SystemConfig { - timestamp: string; - services: { - api: { - port: number; - protocol: string; - host: string; - path: string; - healthPath: string; + timestamp?: string; + services?: { + api?: { + port?: number; + protocol?: string; + host?: string; + path?: string; + healthPath?: string; }; - web: { - port: number; - protocol: string; - host: string; - path: string; + web?: { + port?: number; + protocol?: string; + host?: string; + path?: string; }; - neo4j: { - uri: string; - port: number; - protocol: string; - host: string; + neo4j?: { + uri?: string; + port?: number; + protocol?: string; + host?: string; }; - mcp: { - port: number; - protocol: string; - host: string; - path: string; + mcp?: { + port?: number; + protocol?: string; + host?: string; + path?: string; }; - proxy: { - enabled: boolean; - httpsPort: number; - httpPort: number; - protocol: string; - host: string; - certPath: string | null; - keyPath: string | null; + proxy?: { + enabled?: boolean; + httpsPort?: number; + httpPort?: number; + protocol?: string; + host?: string; + certPath?: string | null; + keyPath?: string | null; }; }; - tls: { - enabled: boolean; - certPath: string | null; - keyPath: string | null; - httpsPort: number | null; + tls?: { + enabled?: boolean; + certPath?: string | null; + keyPath?: string | null; + httpsPort?: number | null; }; - environment: { - nodeEnv: string; - clientUrl: string; - corsOrigin: string; + environment?: { + nodeEnv?: string; + clientUrl?: string; + corsOrigin?: string; }; } @@ -133,28 +133,31 @@ export function useSystemConfig(options: UseSystemConfigOptions = {}) { // Helper functions to get specific configuration values const getApiUrl = () => { - if (!config) return null; - return `${config.services.api.protocol}://${config.services.api.host}:${config.services.api.port}`; + const api = config?.services?.api; + if (!api) return null; + return `${api.protocol}://${api.host}:${api.port}`; }; const getWebUrl = () => { - if (!config) return null; - return `${config.services.web.protocol}://${config.services.web.host}:${config.services.web.port}`; + const web = config?.services?.web; + if (!web) return null; + return `${web.protocol}://${web.host}:${web.port}`; }; const getProxyUrl = () => { - if (!config || !config.services.proxy.enabled) return null; - return `${config.services.proxy.protocol}://${config.services.proxy.host}:${config.services.proxy.httpsPort}`; + const proxy = config?.services?.proxy; + if (!proxy || !proxy.enabled) return null; + return `${proxy.protocol}://${proxy.host}:${proxy.httpsPort}`; }; const getNeo4jUrl = () => { - if (!config) return null; - return config.services.neo4j.uri; + return config?.services?.neo4j?.uri ?? null; }; const getMcpUrl = () => { - if (!config) return null; - return `${config.services.mcp.protocol}://${config.services.mcp.host}:${config.services.mcp.port}`; + const mcp = config?.services?.mcp; + if (!mcp) return null; + return `${mcp.protocol}://${mcp.host}:${mcp.port}`; }; return { diff --git a/packages/web/src/pages/Admin.tsx b/packages/web/src/pages/Admin.tsx index 10123181..7b2a4e2a 100644 --- a/packages/web/src/pages/Admin.tsx +++ b/packages/web/src/pages/Admin.tsx @@ -1279,7 +1279,7 @@ certbot renew --dry-run - {config ? `HTTPS:${config.services.proxy.httpsPort}` : 'HTTPS:8443'} + {`HTTPS:${config?.services?.proxy?.httpsPort ?? '8443'}`} @@ -1296,10 +1296,10 @@ certbot renew --dry-run - {config ? `HTTP:${config.services.web.port}` : 'HTTP:3127'} + {`HTTP:${config?.services?.web?.port ?? '3127'}`} - {config ? `HTTP:${config.services.api.port}` : 'HTTP:4127'} + {`HTTP:${config?.services?.api?.port ?? '4127'}`} @@ -1321,14 +1321,14 @@ certbot renew --dry-run - {config ? `Neo4j:${config.services.neo4j.port}` : 'Neo4j:7687'} + {`Graph DB:${config?.services?.neo4j?.port ?? '7687'}`} - - {/* Neo4j Database */} + + {/* Graph Database */} - Neo4j + Graph DB Database @@ -1360,7 +1360,7 @@ certbot renew --dry-run
- Traffic Flow: Browser → HTTPS ({config?.services.proxy.httpsPort || '8443'}) → Nginx Proxy → HTTP backends ({config?.services.web.port || '3127'} Web, {config?.services.api.port || '4127'} API) → Neo4j ({config?.services.neo4j.port || '7687'}) + Traffic Flow: Browser → HTTPS ({config?.services?.proxy?.httpsPort || '8443'}) → Nginx Proxy → HTTP backends ({config?.services?.web?.port || '3127'} Web, {config?.services?.api?.port || '4127'} API) → Graph DB ({config?.services?.neo4j?.port || '7687'})
{lastUpdated && ( @@ -1767,7 +1767,7 @@ For more details, see: /docs/tls-ssl-setup.md addDebugMessage(`📊 Status code: ${healthResponse.status}`); addDebugMessage(`🔍 Server status: ${healthData.status}`); addDebugMessage(`💾 GraphQL service: ${healthData.services?.graphql?.status || 'unknown'}`); - addDebugMessage(`🗄️ Neo4j status: ${healthData.services?.neo4j?.status || 'unknown'}`); + addDebugMessage(`🗄️ Graph store status: ${healthData.services?.neo4j?.status || 'unknown'}`); if (healthResponse.ok) { addDebugMessage('✅ Health endpoint test PASSED'); diff --git a/packages/web/src/pages/Backend.tsx b/packages/web/src/pages/Backend.tsx index c71c18cc..7c31d4e5 100644 --- a/packages/web/src/pages/Backend.tsx +++ b/packages/web/src/pages/Backend.tsx @@ -90,24 +90,26 @@ export function Backend() { dependencies: ['GraphQL API Server'] }); - // Neo4j Graph Database + // Graph store const neo4jStatus = healthData.services?.neo4j?.status; let neo4jServiceStatus: 'healthy' | 'degraded' | 'down' = 'down'; - let neo4jDescription = 'Neo4j connection status unknown'; - + let neo4jDescription = 'Graph store connection status unknown'; + if (neo4jStatus === 'healthy') { neo4jServiceStatus = 'healthy'; - neo4jDescription = `Connected to ${healthData.services.neo4j.uri}`; - debug.push(`🗄️ Neo4j: healthy at ${healthData.services.neo4j.uri}`); + neo4jDescription = healthData.services?.neo4j?.uri + ? `Connected to ${healthData.services.neo4j.uri}` + : 'Graph store connected'; + debug.push(`🗄️ Graph store: healthy${healthData.services?.neo4j?.uri ? ` at ${healthData.services.neo4j.uri}` : ''}`); } else if (neo4jStatus === 'unhealthy') { neo4jServiceStatus = 'down'; - const error = healthData.services.neo4j.error || 'Connection failed'; + const error = healthData.services?.neo4j?.error || 'Connection failed'; neo4jDescription = `Connection failed: ${error.substring(0, 100)}${error.length > 100 ? '...' : ''}`; - debug.push(`❌ Neo4j: down - ${error}`); + debug.push(`❌ Graph store: down - ${error}`); } - + services.push({ - name: 'Neo4j Graph Database', + name: 'Graph store', status: neo4jServiceStatus, responseTime: neo4jServiceStatus === 'healthy' ? 8 : undefined, lastChecked: now, @@ -197,7 +199,7 @@ export function Backend() { description: 'Unavailable (depends on GraphQL API)' }, { - name: 'Neo4j Graph Database', + name: 'Graph store', status: 'down', lastChecked: new Date(), description: 'Status unknown (server unreachable)' @@ -586,7 +588,7 @@ export function Backend() { SQLite Auth - Neo4j (Optional) + Graph store MCP Server @@ -713,13 +715,13 @@ export function Backend() {
-

Graph Database

+

Graph database

- {systemHealth.services.find(s => s.name === 'Neo4j Graph Database')?.status === 'healthy' ? 'Online' : 'Offline'} + {systemHealth.services.find(s => s.name === 'Graph store')?.status === 'healthy' ? 'Online' : 'Offline'}

s.name === 'Neo4j Graph Database')?.status === 'healthy' + systemHealth.services.find(s => s.name === 'Graph store')?.status === 'healthy' ? 'text-green-400' : 'text-red-400' }`} />