Files
igny8/frontend/src/pages/Settings/DebugStatus.tsx
IGNY8 VPS (Salman) 492a83ebcb sd
2025-11-29 19:50:22 +00:00

977 lines
38 KiB
TypeScript

import React, { useState, useEffect, useCallback, useRef } from "react";
import { AlertTriangle } from "lucide-react";
import PageMeta from "../../components/common/PageMeta";
import ComponentCard from "../../components/common/ComponentCard";
import SiteAndSectorSelector from "../../components/common/SiteAndSectorSelector";
import WordPressIntegrationDebug from "./WordPressIntegrationDebug";
import { API_BASE_URL, fetchAPI } from "../../services/api";
import { useSiteStore } from "../../store/siteStore";
import { useToast } from "../../components/ui/toast/ToastContainer";
interface HealthCheck {
name: string;
description: string;
status: 'healthy' | 'warning' | 'error' | 'checking';
message?: string;
details?: string;
lastChecked?: string;
step?: string;
payloadKeys?: string[];
responseCode?: number;
}
interface ModuleHealth {
module: string;
description: string;
checks: HealthCheck[];
}
interface SyncEvent {
id: string;
timestamp: string;
direction: '📤 IGNY8 → WP' | '📥 WP → IGNY8';
trigger: string;
contentId?: number;
taskId?: number;
status: 'success' | 'partial' | 'failed';
steps: SyncEventStep[];
payload?: any;
response?: any;
}
interface SyncEventStep {
step: string;
file: string;
status: 'success' | 'warning' | 'failed' | 'skipped';
details: string;
error?: string;
duration?: number;
}
interface DataSyncValidation {
field: string;
sentByIgny8: boolean;
receivedByWP: boolean;
storedInWP: boolean;
verifiedInWPPost: boolean;
error?: string;
}
interface IntegrationHealth {
overall: 'healthy' | 'warning' | 'error';
lastSyncIgny8ToWP?: string;
lastSyncWPToIgny8?: string;
lastSiteMetadataCheck?: string;
wpApiReachable: boolean;
wpStatusEndpoint: boolean;
wpMetadataEndpoint: boolean;
apiKeyValid: boolean;
jwtTokenValid: boolean;
}
const getStatusColor = (status: string) => {
switch (status) {
case 'healthy':
case 'success': return 'text-green-600 dark:text-green-400';
case 'warning':
case 'partial': return 'text-yellow-600 dark:text-yellow-400';
case 'error':
case 'failed': return 'text-red-600 dark:text-red-400';
case 'checking': return 'text-blue-600 dark:text-blue-400';
case 'skipped': return 'text-gray-600 dark:text-gray-400';
default: return 'text-gray-600 dark:text-gray-400';
}
};
const getStatusBadge = (status: string) => {
switch (status) {
case 'healthy':
case 'success': return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400';
case 'warning':
case 'partial': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400';
case 'error':
case 'failed': return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400';
case 'checking': return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400';
case 'skipped': return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400';
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'healthy':
case 'success': return '✓';
case 'warning':
case 'partial': return '⚠';
case 'error':
case 'failed': return '✗';
case 'checking': return '⟳';
case 'skipped': return '—';
default: return '?';
}
};
export default function DebugStatus() {
const { activeSite } = useSiteStore();
const toast = useToast();
// Tab navigation state
const [activeTab, setActiveTab] = useState<'system-health' | 'wp-integration'>('system-health');
// Data state
const [loading, setLoading] = useState(false);
const [moduleHealths, setModuleHealths] = useState<ModuleHealth[]>([]);
const [debugEnabled, setDebugEnabled] = useState(false);
// Helper to call API endpoints
const apiCall = useCallback(async (endpoint: string, options: RequestInit = {}) => {
try {
console.log(`[DEBUG] Calling API: ${endpoint}`);
// fetchAPI returns parsed JSON data directly (not Response object)
const data = await fetchAPI(endpoint, options);
console.log(`[DEBUG] Success from ${endpoint}:`, data);
// Return mock response object with data
return {
response: { ok: true, status: 200, statusText: 'OK' } as Response,
data
};
} catch (error: any) {
console.error(`[DEBUG] API call failed for ${endpoint}:`, error);
// fetchAPI throws errors for non-OK responses - extract error data
const errorData = error.response || {
detail: error.message?.replace(/^API Error.*?:\s*/, '') || 'Request failed'
};
const status = error.status || 500;
console.log(`[DEBUG] Error from ${endpoint}:`, { status, errorData });
return {
response: { ok: false, status, statusText: error.message } as Response,
data: errorData
};
}
}, []);
// Check database schema field mappings (the issues we just fixed)
const checkDatabaseSchemaMapping = useCallback(async (): Promise<HealthCheck> => {
if (!activeSite) {
return {
name: 'Database Schema Mapping',
description: 'Checks if model field names map correctly to database columns',
status: 'warning',
message: 'No site selected',
details: 'Please select a site to run health checks',
lastChecked: new Date().toISOString(),
};
}
try {
// Test Writer Content endpoint (was failing with entity_type error)
const { response: contentResp, data: contentData } = await apiCall(`/v1/writer/content/?site=${activeSite.id}`);
if (!contentResp.ok) {
const errorMsg = contentData?.detail || contentData?.error || contentData?.message || `HTTP ${contentResp.status}`;
return {
name: 'Database Schema Mapping',
description: 'Checks if model field names map correctly to database columns',
status: 'error',
message: errorMsg,
details: 'Cannot verify schema - API endpoint failed',
lastChecked: new Date().toISOString(),
};
}
// Check if response has the expected structure with new field names
if (contentData?.results && Array.isArray(contentData.results)) {
// If we can fetch content, schema mapping is working
return {
name: 'Database Schema Mapping',
description: 'Checks if model field names map correctly to database columns',
status: 'healthy',
message: 'All model fields correctly mapped via db_column attributes',
details: `Content API working correctly for ${activeSite.name}. Fields like content_type, content_html, content_structure are properly mapped.`,
lastChecked: new Date().toISOString(),
};
}
return {
name: 'Database Schema Mapping',
description: 'Checks if model field names map correctly to database columns',
status: 'warning',
message: 'Content API returned unexpected structure',
details: 'Response format may have changed',
lastChecked: new Date().toISOString(),
};
} catch (error: any) {
return {
name: 'Database Schema Mapping',
description: 'Checks if model field names map correctly to database columns',
status: 'error',
message: error.message || 'Failed to check schema mapping',
details: 'Check if db_column attributes are set correctly in models',
lastChecked: new Date().toISOString(),
};
}
}, [apiCall, activeSite]);
// Check Writer module health
const checkWriterModule = useCallback(async (): Promise<HealthCheck[]> => {
const checks: HealthCheck[] = [];
if (!activeSite) {
checks.push({
name: 'Content List',
description: 'Writer content listing endpoint',
status: 'warning',
message: 'No site selected',
lastChecked: new Date().toISOString(),
});
return checks;
}
// Check Content endpoint
try {
const { response: contentResp, data: contentData } = await apiCall(`/v1/writer/content/?site=${activeSite.id}`);
if (contentResp && contentResp.ok) {
checks.push({
name: 'Content List',
description: 'Writer content listing endpoint',
status: 'healthy',
message: `Found ${contentData?.count || contentData?.results?.length || 0} content items for ${activeSite.name}`,
lastChecked: new Date().toISOString(),
});
} else {
const errorMsg = contentData?.detail || contentData?.error || contentData?.message || (contentResp ? `HTTP ${contentResp.status}` : 'Request failed');
checks.push({
name: 'Content List',
description: 'Writer content listing endpoint',
status: 'error',
message: errorMsg,
details: 'Check authentication and endpoint availability',
lastChecked: new Date().toISOString(),
});
}
} catch (error: any) {
checks.push({
name: 'Content List',
description: 'Writer content listing endpoint',
status: 'error',
message: error.message || 'Network error',
lastChecked: new Date().toISOString(),
});
}
// Check Tasks endpoint
try {
const { response: tasksResp, data: tasksData } = await apiCall(`/v1/writer/tasks/?site=${activeSite.id}`);
if (tasksResp && tasksResp.ok) {
checks.push({
name: 'Tasks List',
description: 'Writer tasks listing endpoint',
status: 'healthy',
message: `Found ${tasksData?.count || tasksData?.results?.length || 0} tasks for ${activeSite.name}`,
lastChecked: new Date().toISOString(),
});
} else {
const errorMsg = tasksData?.detail || tasksData?.error || tasksData?.message || (tasksResp ? `HTTP ${tasksResp.status}` : 'Request failed');
checks.push({
name: 'Tasks List',
description: 'Writer tasks listing endpoint',
status: 'error',
message: errorMsg,
details: 'Check authentication and endpoint availability',
lastChecked: new Date().toISOString(),
});
}
} catch (error: any) {
checks.push({
name: 'Tasks List',
description: 'Writer tasks listing endpoint',
status: 'error',
message: error.message || 'Network error',
lastChecked: new Date().toISOString(),
});
}
// Check Content Validation
try {
// Get first content ID if available
const { data: contentData } = await apiCall(`/v1/writer/content/?site=${activeSite.id}`);
const firstContentId = contentData?.results?.[0]?.id;
if (firstContentId) {
const { response: validationResp, data: validationData } = await apiCall(
`/v1/writer/content/${firstContentId}/validation/`
);
if (validationResp.ok && validationData?.success !== false) {
checks.push({
name: 'Content Validation',
description: 'Content validation before publish',
status: 'healthy',
message: 'Validation endpoint working',
lastChecked: new Date().toISOString(),
});
} else {
checks.push({
name: 'Content Validation',
description: 'Content validation before publish',
status: 'error',
message: validationData?.error || `Failed with ${validationResp.status}`,
details: 'Check validation_service.py field references (content_html, content_type)',
lastChecked: new Date().toISOString(),
});
}
} else {
checks.push({
name: 'Content Validation',
description: 'Content validation before publish',
status: 'warning',
message: 'No content available to test validation',
lastChecked: new Date().toISOString(),
});
}
} catch (error: any) {
checks.push({
name: 'Content Validation',
description: 'Content validation before publish',
status: 'error',
message: error.message || 'Network error',
lastChecked: new Date().toISOString(),
});
}
return checks;
}, [apiCall, activeSite]);
// Check Planner module health
const checkPlannerModule = useCallback(async (): Promise<HealthCheck[]> => {
const checks: HealthCheck[] = [];
if (!activeSite) {
checks.push({
name: 'Keyword Clusters',
description: 'Planner keyword clustering endpoint',
status: 'warning',
message: 'No site selected',
lastChecked: new Date().toISOString(),
});
return checks;
}
// Check Keyword Clusters endpoint
try {
const { response: clustersResp, data: clustersData } = await apiCall(`/v1/planner/clusters/?site=${activeSite.id}`);
if (clustersResp && clustersResp.ok) {
checks.push({
name: 'Clusters List',
description: 'Planner clusters listing endpoint',
status: 'healthy',
message: `Found ${clustersData?.count || clustersData?.results?.length || 0} clusters for ${activeSite.name}`,
lastChecked: new Date().toISOString(),
});
} else {
const errorMsg = clustersData?.detail || clustersData?.error || clustersData?.message || (clustersResp ? `HTTP ${clustersResp.status}` : 'Request failed');
checks.push({
name: 'Clusters List',
description: 'Planner clusters listing endpoint',
status: 'error',
message: errorMsg,
lastChecked: new Date().toISOString(),
});
}
} catch (error: any) {
checks.push({
name: 'Clusters List',
description: 'Planner clusters listing endpoint',
status: 'error',
message: error.message || 'Network error',
lastChecked: new Date().toISOString(),
});
}
// Check Keywords endpoint
try {
const { response: keywordsResp, data: keywordsData } = await apiCall(`/v1/planner/keywords/?site=${activeSite.id}`);
if (keywordsResp.ok && keywordsData?.success !== false) {
checks.push({
name: 'Keywords List',
description: 'Planner keywords listing endpoint',
status: 'healthy',
message: `Found ${keywordsData?.count || 0} keywords for ${activeSite.name}`,
lastChecked: new Date().toISOString(),
});
} else {
checks.push({
name: 'Keywords List',
description: 'Planner keywords listing endpoint',
status: 'error',
message: keywordsData?.error || `Failed with ${keywordsResp.status}`,
lastChecked: new Date().toISOString(),
});
}
} catch (error: any) {
checks.push({
name: 'Keywords List',
description: 'Planner keywords listing endpoint',
status: 'error',
message: error.message || 'Network error',
lastChecked: new Date().toISOString(),
});
}
// Check Ideas endpoint
try {
const { response: ideasResp, data: ideasData } = await apiCall(`/v1/planner/ideas/?site=${activeSite.id}`);
if (ideasResp.ok && ideasData?.success !== false) {
checks.push({
name: 'Ideas List',
description: 'Planner ideas listing endpoint',
status: 'healthy',
message: `Found ${ideasData?.count || 0} ideas for ${activeSite.name}`,
lastChecked: new Date().toISOString(),
});
} else {
checks.push({
name: 'Ideas List',
description: 'Planner ideas listing endpoint',
status: 'error',
message: ideasData?.error || `Failed with ${ideasResp.status}`,
lastChecked: new Date().toISOString(),
});
}
} catch (error: any) {
checks.push({
name: 'Ideas List',
description: 'Planner ideas listing endpoint',
status: 'error',
message: error.message || 'Network error',
lastChecked: new Date().toISOString(),
});
}
return checks;
}, [apiCall, activeSite]);
// Check Sites module health
const checkSitesModule = useCallback(async (): Promise<HealthCheck[]> => {
const checks: HealthCheck[] = [];
// Check Sites list
try {
const { response: sitesResp, data: sitesData } = await apiCall('/v1/auth/sites/');
if (sitesResp && sitesResp.ok) {
checks.push({
name: 'Sites List',
description: 'Sites listing endpoint',
status: 'healthy',
message: `Found ${sitesData?.count || sitesData?.results?.length || sitesData?.length || 0} sites`,
lastChecked: new Date().toISOString(),
});
} else {
const errorMsg = sitesData?.detail || sitesData?.error || sitesData?.message || (sitesResp ? `HTTP ${sitesResp.status}` : 'Request failed');
checks.push({
name: 'Sites List',
description: 'Sites listing endpoint',
status: 'error',
message: errorMsg,
lastChecked: new Date().toISOString(),
});
}
} catch (error: any) {
checks.push({
name: 'Sites List',
description: 'Sites listing endpoint',
status: 'error',
message: error.message || 'Network error',
lastChecked: new Date().toISOString(),
});
}
return checks;
}, [apiCall]);
// Check Integration module health
const checkIntegrationModule = useCallback(async (): Promise<HealthCheck[]> => {
const checks: HealthCheck[] = [];
if (!activeSite) {
checks.push({
name: 'Content Types Sync',
description: 'Integration content types endpoint',
status: 'warning',
message: 'No site selected',
lastChecked: new Date().toISOString(),
});
return checks;
}
// Check Integration content types endpoint
try {
// First get the integration for this site
const { response: intResp, data: intData } = await apiCall(
`/v1/integration/integrations/?site_id=${activeSite.id}`
);
if (!intResp.ok || !intData || (Array.isArray(intData) && intData.length === 0) || (intData.results && intData.results.length === 0)) {
checks.push({
name: 'Content Types Sync',
description: 'Integration content types endpoint',
status: 'warning',
message: 'No WordPress integration configured',
details: 'Add a WordPress integration in Settings > Integrations',
lastChecked: new Date().toISOString(),
});
} else {
// Extract integration ID from response
const integration = Array.isArray(intData) ? intData[0] : (intData.results ? intData.results[0] : intData);
const integrationId = integration?.id;
if (!integrationId) {
checks.push({
name: 'Content Types Sync',
description: 'Integration content types endpoint',
status: 'error',
message: 'Invalid integration data',
lastChecked: new Date().toISOString(),
});
} else {
// Now check content types for this integration
const { response: contentTypesResp, data: contentTypesData } = await apiCall(
`/v1/integration/integrations/${integrationId}/content-types/`
);
if (contentTypesResp.ok && contentTypesData?.success !== false) {
checks.push({
name: 'Content Types Sync',
description: 'Integration content types endpoint',
status: 'healthy',
message: `Content types synced for ${activeSite.name}`,
lastChecked: new Date().toISOString(),
});
} else {
checks.push({
name: 'Content Types Sync',
description: 'Integration content types endpoint',
status: 'error',
message: contentTypesData?.detail || contentTypesData?.error || `Failed with ${contentTypesResp.status}`,
details: 'Check integration views field mappings (content_type_map vs entity_type_map)',
lastChecked: new Date().toISOString(),
});
}
}
}
} catch (error: any) {
checks.push({
name: 'Content Types Sync',
description: 'Integration content types endpoint',
status: 'error',
message: error.message || 'Network error',
lastChecked: new Date().toISOString(),
});
}
return checks;
}, [apiCall, activeSite]);
// Run all health checks
const runAllChecks = useCallback(async () => {
setLoading(true);
try {
// Run schema check first
const schemaCheck = await checkDatabaseSchemaMapping();
// Run module checks in parallel
const [writerChecks, plannerChecks, sitesChecks, integrationChecks] = await Promise.all([
checkWriterModule(),
checkPlannerModule(),
checkSitesModule(),
checkIntegrationModule(),
]);
// Build module health results
const moduleHealthResults: ModuleHealth[] = [
{
module: 'Database Schema',
description: 'Critical database field mapping checks',
checks: [schemaCheck],
},
{
module: 'Writer Module',
description: 'Content creation and task management',
checks: writerChecks,
},
{
module: 'Planner Module',
description: 'Keyword clustering and content planning',
checks: plannerChecks,
},
{
module: 'Sites Module',
description: 'Site management and configuration',
checks: sitesChecks,
},
{
module: 'Integration Module',
description: 'External platform sync (WordPress, etc.)',
checks: integrationChecks,
},
];
setModuleHealths(moduleHealthResults);
} catch (error) {
console.error('Failed to run health checks:', error);
} finally {
setLoading(false);
}
}, [
apiCall,
checkDatabaseSchemaMapping,
checkWriterModule,
checkPlannerModule,
checkSitesModule,
checkIntegrationModule,
]);
// Run checks on mount and when site changes
useEffect(() => {
if (!debugEnabled || !activeSite) {
setModuleHealths([]);
return;
}
runAllChecks();
}, [runAllChecks, debugEnabled, activeSite]);
// Calculate module status
const getModuleStatus = (module: ModuleHealth): 'error' | 'warning' | 'healthy' => {
const statuses = module.checks.map(c => c.status);
if (statuses.some(s => s === 'error')) return 'error';
if (statuses.some(s => s === 'warning')) return 'warning';
return 'healthy';
};
// Calculate overall health
const getOverallHealth = () => {
const allStatuses = moduleHealths.flatMap(m => m.checks.map(c => c.status));
const total = allStatuses.length;
const healthy = allStatuses.filter(s => s === 'healthy').length;
const warning = allStatuses.filter(s => s === 'warning').length;
const error = allStatuses.filter(s => s === 'error').length;
let status: 'error' | 'warning' | 'healthy' = 'healthy';
if (error > 0) status = 'error';
else if (warning > 0) status = 'warning';
return { total, healthy, warning, error, status, percentage: total > 0 ? Math.round((healthy / total) * 100) : 0 };
};
const overallHealth = getOverallHealth();
return (
<>
<PageMeta title="Debug Status - IGNY8" description="Module health checks and diagnostics" />
<div className="space-y-6">
{/* Header */}
<div className="flex justify-between items-center">
<div>
<h1 className="text-2xl font-semibold text-gray-800 dark:text-white/90">Debug Status</h1>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
Comprehensive health checks for all modules and recent bug fixes
</p>
</div>
<button
onClick={runAllChecks}
disabled={loading}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
>
{loading ? 'Running Checks...' : 'Refresh All'}
</button>
</div>
{/* Site Selector */}
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-4">
<SiteAndSectorSelector hideSectorSelector={true} />
</div>
{/* No Site Selected Warning */}
{!activeSite && (
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-900/50 rounded-lg p-4">
<div className="flex items-start space-x-3">
<AlertTriangle className="h-5 w-5 text-yellow-500 mt-0.5" />
<div>
<p className="text-sm text-yellow-800 dark:text-yellow-200 font-medium">No Site Selected</p>
<p className="text-xs text-yellow-600 dark:text-yellow-300 mt-1">
Please select a site above to run health checks and view debug information.
</p>
</div>
</div>
</div>
)}
{/* Debug Toggle */}
{activeSite && (
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-medium text-gray-900 dark:text-white">System Health Debug</h3>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Enable debug mode to run health checks for {activeSite.name}
</p>
</div>
<label className="flex items-center space-x-2 cursor-pointer">
<input
type="checkbox"
checked={debugEnabled}
onChange={(e) => setDebugEnabled(e.target.checked)}
className="rounded"
/>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
{debugEnabled ? 'Enabled' : 'Disabled'}
</span>
</label>
</div>
</div>
)}
{/* Tab Content */}
{debugEnabled && activeSite ? (
<>
{/* Tab Navigation */}
<div className="bg-white dark:bg-gray-800 shadow rounded-lg">
<div className="border-b border-gray-200 dark:border-gray-700">
<nav className="flex space-x-8 px-6">
<button
onClick={() => setActiveTab('system-health')}
className={`py-4 px-2 border-b-2 font-medium text-sm transition-colors ${
activeTab === 'system-health'
? 'border-blue-500 text-blue-600 dark:text-blue-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
System Health
</button>
<button
onClick={() => setActiveTab('wp-integration')}
className={`py-4 px-2 border-b-2 font-medium text-sm transition-colors ${
activeTab === 'wp-integration'
? 'border-blue-500 text-blue-600 dark:text-blue-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
IGNY8 WordPress
</button>
</nav>
</div>
</div>
{/* Tab Content */}
{activeTab === 'system-health' ? (
<div className="space-y-6">
{/* Overall Health Summary */}
<ComponentCard
title={
<div className="flex items-center gap-2">
<span>Overall System Health</span>
<span className={`text-lg ${getStatusColor(overallHealth.status)}`}>
{getStatusIcon(overallHealth.status)}
</span>
</div>
}
desc={
overallHealth.status === 'error'
? `${overallHealth.error} critical issue${overallHealth.error !== 1 ? 's' : ''} detected`
: overallHealth.status === 'warning'
? `${overallHealth.warning} warning${overallHealth.warning !== 1 ? 's' : ''} detected`
: 'All systems operational'
}
>
<div className="space-y-4">
{/* Health Percentage */}
<div className="text-center">
<div className={`text-5xl font-bold ${getStatusColor(overallHealth.status)}`}>
{overallHealth.percentage}%
</div>
<div className="text-sm text-gray-600 dark:text-gray-400 mt-2">
{overallHealth.healthy} of {overallHealth.total} checks passed
</div>
</div>
{/* Health Breakdown */}
<div className="grid grid-cols-3 gap-4 pt-4 border-t border-gray-200 dark:border-gray-700">
<div className="text-center">
<div className={`text-2xl font-semibold ${getStatusColor('healthy')}`}>
{overallHealth.healthy}
</div>
<div className="text-xs text-gray-500 dark:text-gray-500 mt-1">Healthy</div>
</div>
<div className="text-center">
<div className={`text-2xl font-semibold ${getStatusColor('warning')}`}>
{overallHealth.warning}
</div>
<div className="text-xs text-gray-500 dark:text-gray-500 mt-1">Warnings</div>
</div>
<div className="text-center">
<div className={`text-2xl font-semibold ${getStatusColor('error')}`}>
{overallHealth.error}
</div>
<div className="text-xs text-gray-500 dark:text-gray-500 mt-1">Errors</div>
</div>
</div>
</div>
</ComponentCard>
{/* Module Health Cards */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{moduleHealths.map((moduleHealth, index) => {
const moduleStatus = getModuleStatus(moduleHealth);
const healthyCount = moduleHealth.checks.filter(c => c.status === 'healthy').length;
const totalCount = moduleHealth.checks.length;
return (
<ComponentCard
key={index}
title={
<div className="flex items-center gap-2">
<span>{moduleHealth.module}</span>
<span className={`text-xs px-2 py-0.5 rounded ${getStatusBadge(moduleStatus)}`}>
{getStatusIcon(moduleStatus)}
</span>
</div>
}
desc={
moduleStatus === 'error'
? `Issues detected - ${healthyCount}/${totalCount} checks passed`
: moduleStatus === 'warning'
? `Warnings detected - ${healthyCount}/${totalCount} checks passed`
: `All ${totalCount} checks passed`
}
>
<div className="space-y-3">
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
{moduleHealth.description}
</p>
{/* Health Checks List */}
{moduleHealth.checks.map((check, checkIndex) => (
<div
key={checkIndex}
className={`p-3 rounded-lg border ${
check.status === 'healthy'
? 'border-green-200 dark:border-green-900/50 bg-green-50 dark:bg-green-900/20'
: check.status === 'warning'
? 'border-yellow-200 dark:border-yellow-900/50 bg-yellow-50 dark:bg-yellow-900/20'
: check.status === 'error'
? 'border-red-200 dark:border-red-900/50 bg-red-50 dark:bg-red-900/20'
: 'border-blue-200 dark:border-blue-900/50 bg-blue-50 dark:bg-blue-900/20'
}`}
>
<div className="flex items-start gap-2">
<span className={`text-lg ${getStatusColor(check.status)} flex-shrink-0`}>
{getStatusIcon(check.status)}
</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="font-medium text-gray-800 dark:text-white/90">
{check.name}
</span>
<span className={`text-xs px-2 py-0.5 rounded ${getStatusBadge(check.status)}`}>
{check.status}
</span>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
{check.description}
</p>
{check.message && (
<p className={`text-sm ${getStatusColor(check.status)} font-medium`}>
{check.message}
</p>
)}
{check.details && (
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1 italic">
💡 {check.details}
</p>
)}
</div>
</div>
</div>
))}
</div>
</ComponentCard>
);
})}
</div>
{/* Help Section */}
<ComponentCard
title="Troubleshooting Guide"
desc="Common issues and solutions"
>
<div className="space-y-4">
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-900/50">
<h3 className="font-medium text-gray-800 dark:text-white/90 mb-2">
Database Schema Mapping Errors
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">
If you see errors about missing fields like <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_type</code>,
<code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs ml-1">content_structure</code>, or
<code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs ml-1">content_html</code>:
</p>
<ul className="text-sm text-gray-600 dark:text-gray-400 list-disc list-inside space-y-1">
<li>Check that model fields match database column names</li>
<li>Verify database columns exist with <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">SELECT column_name FROM information_schema.columns</code></li>
<li>All field names now match database (no db_column mappings)</li>
</ul>
</div>
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-900/50">
<h3 className="font-medium text-gray-800 dark:text-white/90 mb-2">
Field Reference Errors in Code
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">
If API endpoints return 500 errors with AttributeError or similar:
</p>
<ul className="text-sm text-gray-600 dark:text-gray-400 list-disc list-inside space-y-1">
<li>All field names now standardized: <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_type</code>, <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_structure</code>, <code className="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-xs">content_html</code></li>
<li>Old names removed: entity_type, site_entity_type, cluster_role, html_content</li>
<li>Check views, services, and serializers in writer/planner/integration modules</li>
</ul>
</div>
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-900/50">
<h3 className="font-medium text-gray-800 dark:text-white/90 mb-2">
All Checks Passing?
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Great! Your system is healthy. This page will help you quickly diagnose issues if they appear in the future.
Bookmark this page and check it first when troubleshooting module-specific problems.
</p>
</div>
</div>
</ComponentCard>
</div>
) : (
// WordPress Integration Debug Tab
<WordPressIntegrationDebug />
)}
</>
) : (
<div className="bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-gray-700 rounded-lg p-6 text-center">
<AlertTriangle className="h-8 w-8 text-gray-400 mx-auto mb-2" />
<p className="text-gray-600 dark:text-gray-400">
{activeSite
? 'Enable debug mode above to view system health checks'
: 'Select a site and enable debug mode to view system health checks'}
</p>
</div>
)}
</div>
</>
);
}