This commit is contained in:
IGNY8 VPS (Salman)
2025-11-29 19:50:22 +00:00
parent 302e14196c
commit 492a83ebcb
4 changed files with 569 additions and 294 deletions

View File

@@ -1,4 +1,5 @@
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";
@@ -120,20 +121,60 @@ export default function DebugStatus() {
// 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/');
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: `Writer Content API failed with ${contentResp.status}`,
details: 'Model fields may not be mapped to database columns correctly (e.g., content_type vs entity_type)',
message: errorMsg,
details: 'Cannot verify schema - API endpoint failed',
lastChecked: new Date().toISOString(),
};
}
@@ -146,7 +187,7 @@ export default function DebugStatus() {
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. Fields like content_type, content_html, content_structure are properly mapped.`,
details: `Content API working correctly for ${activeSite.name}. Fields like content_type, content_html, content_structure are properly mapped.`,
lastChecked: new Date().toISOString(),
};
}
@@ -169,31 +210,43 @@ export default function DebugStatus() {
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/');
const { response: contentResp, data: contentData } = await apiCall(`/v1/writer/content/?site=${activeSite.id}`);
if (contentResp.ok && contentData?.success !== false) {
if (contentResp && contentResp.ok) {
checks.push({
name: 'Content List',
description: 'Writer content listing endpoint',
status: 'healthy',
message: `Found ${contentData?.count || 0} content items`,
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: contentData?.error || `Failed with ${contentResp.status}`,
details: 'Check model field mappings (content_type, content_html, content_structure)',
message: errorMsg,
details: 'Check authentication and endpoint availability',
lastChecked: new Date().toISOString(),
});
}
@@ -209,23 +262,24 @@ export default function DebugStatus() {
// Check Tasks endpoint
try {
const { response: tasksResp, data: tasksData } = await apiCall('/v1/writer/tasks/');
const { response: tasksResp, data: tasksData } = await apiCall(`/v1/writer/tasks/?site=${activeSite.id}`);
if (tasksResp.ok && tasksData?.success !== false) {
if (tasksResp && tasksResp.ok) {
checks.push({
name: 'Tasks List',
description: 'Writer tasks listing endpoint',
status: 'healthy',
message: `Found ${tasksData?.count || 0} tasks`,
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: tasksData?.error || `Failed with ${tasksResp.status}`,
details: 'Check model field mappings (content_type, content_structure, taxonomy_term)',
message: errorMsg,
details: 'Check authentication and endpoint availability',
lastChecked: new Date().toISOString(),
});
}
@@ -242,7 +296,7 @@ export default function DebugStatus() {
// Check Content Validation
try {
// Get first content ID if available
const { data: contentData } = await apiCall('/v1/writer/content/');
const { data: contentData } = await apiCall(`/v1/writer/content/?site=${activeSite.id}`);
const firstContentId = contentData?.results?.[0]?.id;
if (firstContentId) {
@@ -288,30 +342,42 @@ export default function DebugStatus() {
}
return checks;
}, []);
}, [apiCall, activeSite]);
// Check Planner module health
const checkPlannerModule = useCallback(async (): Promise<HealthCheck[]> => {
const checks: HealthCheck[] = [];
// Check Clusters endpoint
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/');
const { response: clustersResp, data: clustersData } = await apiCall(`/v1/planner/clusters/?site=${activeSite.id}`);
if (clustersResp.ok && clustersData?.success !== false) {
if (clustersResp && clustersResp.ok) {
checks.push({
name: 'Clusters List',
description: 'Planner clusters listing endpoint',
status: 'healthy',
message: `Found ${clustersData?.count || 0} clusters`,
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: clustersData?.error || `Failed with ${clustersResp.status}`,
message: errorMsg,
lastChecked: new Date().toISOString(),
});
}
@@ -327,14 +393,14 @@ export default function DebugStatus() {
// Check Keywords endpoint
try {
const { response: keywordsResp, data: keywordsData } = await apiCall('/v1/planner/keywords/');
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`,
message: `Found ${keywordsData?.count || 0} keywords for ${activeSite.name}`,
lastChecked: new Date().toISOString(),
});
} else {
@@ -358,14 +424,14 @@ export default function DebugStatus() {
// Check Ideas endpoint
try {
const { response: ideasResp, data: ideasData } = await apiCall('/v1/planner/ideas/');
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`,
message: `Found ${ideasData?.count || 0} ideas for ${activeSite.name}`,
lastChecked: new Date().toISOString(),
});
} else {
@@ -388,7 +454,7 @@ export default function DebugStatus() {
}
return checks;
}, []);
}, [apiCall, activeSite]);
// Check Sites module health
const checkSitesModule = useCallback(async (): Promise<HealthCheck[]> => {
@@ -398,20 +464,21 @@ export default function DebugStatus() {
try {
const { response: sitesResp, data: sitesData } = await apiCall('/v1/auth/sites/');
if (sitesResp.ok && sitesData?.success !== false) {
if (sitesResp && sitesResp.ok) {
checks.push({
name: 'Sites List',
description: 'Sites listing endpoint',
status: 'healthy',
message: `Found ${sitesData?.count || sitesData?.results?.length || 0} sites`,
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: sitesData?.error || `Failed with ${sitesResp.status}`,
message: errorMsg,
lastChecked: new Date().toISOString(),
});
}
@@ -426,49 +493,77 @@ export default function DebugStatus() {
}
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 {
// Get first site ID if available
const { data: sitesData } = await apiCall('/v1/auth/sites/');
const firstSiteId = sitesData?.results?.[0]?.id || sitesData?.[0]?.id;
// First get the integration for this site
const { response: intResp, data: intData } = await apiCall(
`/v1/integration/integrations/?site_id=${activeSite.id}`
);
if (firstSiteId) {
const { response: contentTypesResp, data: contentTypesData } = await apiCall(
`/v1/integration/integrations/${firstSiteId}/content-types/`
);
if (contentTypesResp.ok && contentTypesData?.success !== false) {
checks.push({
name: 'Content Types Sync',
description: 'Integration content types endpoint',
status: 'healthy',
message: 'Content types endpoint working',
lastChecked: new Date().toISOString(),
});
} else {
checks.push({
name: 'Content Types Sync',
description: 'Integration content types endpoint',
status: 'error',
message: contentTypesData?.error || `Failed with ${contentTypesResp.status}`,
details: 'Check integration views field mappings (content_type_map vs entity_type_map)',
lastChecked: new Date().toISOString(),
});
}
} else {
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 sites available to test integration',
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({
@@ -481,7 +576,7 @@ export default function DebugStatus() {
}
return checks;
}, []);
}, [apiCall, activeSite]);
// Run all health checks
const runAllChecks = useCallback(async () => {
@@ -535,6 +630,7 @@ export default function DebugStatus() {
setLoading(false);
}
}, [
apiCall,
checkDatabaseSchemaMapping,
checkWriterModule,
checkPlannerModule,
@@ -542,10 +638,14 @@ export default function DebugStatus() {
checkIntegrationModule,
]);
// Run checks on mount
// Run checks on mount and when site changes
useEffect(() => {
if (!debugEnabled || !activeSite) {
setModuleHealths([]);
return;
}
runAllChecks();
}, [runAllChecks]);
}, [runAllChecks, debugEnabled, activeSite]);
// Calculate module status
const getModuleStatus = (module: ModuleHealth): 'error' | 'warning' | 'healthy' => {
@@ -594,37 +694,85 @@ export default function DebugStatus() {
</button>
</div>
{/* 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>
{/* 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 */}
{activeTab === 'system-health' ? (
<div className="space-y-6">
{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={
@@ -806,10 +954,21 @@ export default function DebugStatus() {
</div>
</div>
</ComponentCard>
</div>
</div>
) : (
// WordPress Integration Debug Tab
<WordPressIntegrationDebug />
)}
</>
) : (
// 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>
</>