Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/web/src/components/InteractiveGraphVisualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')) {
Expand Down Expand Up @@ -4506,7 +4506,7 @@ export function InteractiveGraphVisualization({ onResetLayout, onNodeSelected }:
<div>💡 <strong>Quick fixes:</strong></div>
<div>• Run <code className="bg-gray-800 px-2 py-1 rounded">./start</code> to start the server</div>
<div>• Check if port 4127 is available</div>
<div>• Verify Neo4j database is running</div>
<div>• Verify the graph backend is reachable</div>
</div>
)}

Expand Down
16 changes: 10 additions & 6 deletions packages/web/src/hooks/useHealthCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}

Expand Down Expand Up @@ -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]);
Expand Down
9 changes: 4 additions & 5 deletions packages/web/src/hooks/useHealthStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
};
}

Expand Down Expand Up @@ -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;
Expand All @@ -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 };
}
103 changes: 53 additions & 50 deletions packages/web/src/hooks/useSystemConfig.ts
Original file line number Diff line number Diff line change
@@ -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;
};
}

Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 9 additions & 9 deletions packages/web/src/pages/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,7 @@ certbot renew --dry-run
<g transform="translate(120, 95)">
<line x1="0" y1="10" x2="80" y2="10" stroke="rgb(34, 197, 94)" strokeWidth="2" markerEnd="url(#arrowhead)"/>
<text x="40" y="0" textAnchor="middle" className="fill-green-400 text-xs font-medium">
{config ? `HTTPS:${config.services.proxy.httpsPort}` : 'HTTPS:8443'}
{`HTTPS:${config?.services?.proxy?.httpsPort ?? '8443'}`}
</text>
</g>

Expand All @@ -1296,10 +1296,10 @@ certbot renew --dry-run
<line x1="0" y1="10" x2="50" y2="-20" stroke="rgb(34, 197, 94)" strokeWidth="2" markerEnd="url(#arrowhead)"/>
<line x1="0" y1="10" x2="50" y2="40" stroke="rgb(34, 197, 94)" strokeWidth="2" markerEnd="url(#arrowhead)"/>
<text x="25" y="-10" textAnchor="middle" className="fill-green-400 text-xs">
{config ? `HTTP:${config.services.web.port}` : 'HTTP:3127'}
{`HTTP:${config?.services?.web?.port ?? '3127'}`}
</text>
<text x="25" y="55" textAnchor="middle" className="fill-green-400 text-xs">
{config ? `HTTP:${config.services.api.port}` : 'HTTP:4127'}
{`HTTP:${config?.services?.api?.port ?? '4127'}`}
</text>
</g>

Expand All @@ -1321,14 +1321,14 @@ certbot renew --dry-run
<g transform="translate(540, 140)">
<line x1="0" y1="5" x2="80" y2="5" stroke="rgb(14, 165, 233)" strokeWidth="2" markerEnd="url(#arrowhead)"/>
<text x="40" y="-5" textAnchor="middle" className="fill-sky-400 text-xs">
{config ? `Neo4j:${config.services.neo4j.port}` : 'Neo4j:7687'}
{`Graph DB:${config?.services?.neo4j?.port ?? '7687'}`}
</text>
</g>
{/* Neo4j Database */}

{/* Graph Database */}
<g transform="translate(640, 120)">
<rect x="0" y="0" width="100" height="50" rx="6" fill="rgb(14, 165, 233)" fillOpacity="0.2" stroke="rgb(14, 165, 233)" strokeWidth="2"/>
<text x="50" y="20" textAnchor="middle" className="fill-sky-300 text-sm font-medium">Neo4j</text>
<text x="50" y="20" textAnchor="middle" className="fill-sky-300 text-sm font-medium">Graph DB</text>
<text x="50" y="35" textAnchor="middle" className="fill-sky-300 text-xs">Database</text>
</g>

Expand Down Expand Up @@ -1360,7 +1360,7 @@ certbot renew --dry-run
</div>

<div className="mt-3 text-xs text-gray-400" title="Traffic flow explanation">
<strong className="text-green-400">Traffic Flow:</strong> 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'})
<strong className="text-green-400">Traffic Flow:</strong> 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'})
</div>

{lastUpdated && (
Expand Down Expand Up @@ -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');
Expand Down
30 changes: 16 additions & 14 deletions packages/web/src/pages/Backend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)'
Expand Down Expand Up @@ -586,7 +588,7 @@ export function Backend() {
<text x="165" y="467" textAnchor="middle" className="fill-pink-200 text-xs">SQLite Auth</text>

<rect x="250" y="450" width="130" height="25" fill="#9d174d" stroke="#ec4899" rx="4" />
<text x="315" y="467" textAnchor="middle" className="fill-pink-200 text-xs">Neo4j (Optional)</text>
<text x="315" y="467" textAnchor="middle" className="fill-pink-200 text-xs">Graph store</text>

<rect x="400" y="450" width="130" height="25" fill="#9d174d" stroke="#ec4899" rx="4" />
<text x="465" y="467" textAnchor="middle" className="fill-pink-200 text-xs">MCP Server</text>
Expand Down Expand Up @@ -713,13 +715,13 @@ export function Backend() {
<div className="bg-gray-800 border border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-400">Graph Database</p>
<p className="text-sm text-gray-400">Graph database</p>
<p className="text-2xl font-bold text-gray-100">
{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'}
</p>
</div>
<Database className={`h-8 w-8 ${
systemHealth.services.find(s => s.name === 'Neo4j Graph Database')?.status === 'healthy'
systemHealth.services.find(s => s.name === 'Graph store')?.status === 'healthy'
? 'text-green-400' : 'text-red-400'
}`} />
</div>
Expand Down
Loading