Update Status.tsx
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import PageMeta from "../../components/common/PageMeta";
|
import PageMeta from "../../components/common/PageMeta";
|
||||||
import ComponentCard from "../../components/common/ComponentCard";
|
import ComponentCard from "../../components/common/ComponentCard";
|
||||||
import { fetchAPI, API_BASE_URL } from "../../services/api";
|
import { fetchAPI } from "../../services/api";
|
||||||
|
|
||||||
interface SystemStatus {
|
interface SystemStatus {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
@@ -313,8 +313,46 @@ export default function Status() {
|
|||||||
</div>
|
</div>
|
||||||
</ComponentCard>
|
</ComponentCard>
|
||||||
|
|
||||||
{/* API Monitoring Status Card */}
|
{/* API Status Card */}
|
||||||
<APIMonitoringCard />
|
<ComponentCard title="API Status" desc="API endpoint availability and health">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-gray-600 dark:text-gray-400">API Server</span>
|
||||||
|
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.database?.status || 'unknown')}`}>
|
||||||
|
{status.database?.connected ? 'Operational' : 'Offline'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Base URL: {typeof window !== 'undefined' ? window.location.origin.replace('app.', 'api.') : 'api.igny8.com'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-gray-600 dark:text-gray-400">Response Format</span>
|
||||||
|
<span className="text-xs px-2 py-1 rounded bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400">
|
||||||
|
Unified
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
All endpoints use standardized response format
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="text-xs text-gray-500 dark:text-gray-400 space-y-1">
|
||||||
|
<div>• Health Check: <span className="text-green-600 dark:text-green-400">/api/ping/</span></div>
|
||||||
|
<div>• System Status: <span className="text-green-600 dark:text-green-400">/v1/system/status/</span></div>
|
||||||
|
<div>• Authentication: <span className="text-green-600 dark:text-green-400">/v1/auth/</span></div>
|
||||||
|
<div>• Planner API: <span className="text-green-600 dark:text-green-400">/v1/planner/</span></div>
|
||||||
|
<div>• Writer API: <span className="text-green-600 dark:text-green-400">/v1/writer/</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ComponentCard>
|
||||||
|
|
||||||
{/* Last Updated */}
|
{/* Last Updated */}
|
||||||
<div className="text-center text-sm text-gray-500 dark:text-gray-400">
|
<div className="text-center text-sm text-gray-500 dark:text-gray-400">
|
||||||
@@ -324,229 +362,3 @@ export default function Status() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Monitoring Component
|
|
||||||
interface APIEndpointStatus {
|
|
||||||
name: string;
|
|
||||||
endpoint: string;
|
|
||||||
status: 'healthy' | 'warning' | 'critical' | 'checking';
|
|
||||||
responseTime: number | null;
|
|
||||||
statusCode: number | null;
|
|
||||||
lastChecked: Date | null;
|
|
||||||
error: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function APIMonitoringCard() {
|
|
||||||
const initialEndpoints: APIEndpointStatus[] = [
|
|
||||||
{ name: 'Health Check', endpoint: '/api/ping/', status: 'checking', responseTime: null, statusCode: null, lastChecked: null, error: null },
|
|
||||||
{ name: 'System Status', endpoint: '/v1/system/status/', status: 'checking', responseTime: null, statusCode: null, lastChecked: null, error: null },
|
|
||||||
{ name: 'Auth Endpoint', endpoint: '/v1/auth/me/', status: 'checking', responseTime: null, statusCode: null, lastChecked: null, error: null },
|
|
||||||
{ name: 'Planner API', endpoint: '/v1/planner/keywords/?page=1&page_size=1', status: 'checking', responseTime: null, statusCode: null, lastChecked: null, error: null },
|
|
||||||
{ name: 'Writer API', endpoint: '/v1/writer/tasks/?page=1&page_size=1', status: 'checking', responseTime: null, statusCode: null, lastChecked: null, error: null },
|
|
||||||
];
|
|
||||||
|
|
||||||
const [endpoints, setEndpoints] = useState<APIEndpointStatus[]>(initialEndpoints);
|
|
||||||
const endpointsRef = useRef(initialEndpoints);
|
|
||||||
|
|
||||||
// Keep ref in sync with state
|
|
||||||
useEffect(() => {
|
|
||||||
endpointsRef.current = endpoints;
|
|
||||||
}, [endpoints]);
|
|
||||||
|
|
||||||
const checkEndpoint = async (endpoint: APIEndpointStatus) => {
|
|
||||||
const startTime = performance.now();
|
|
||||||
try {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}${endpoint.endpoint}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
const endTime = performance.now();
|
|
||||||
const responseTime = Math.round(endTime - startTime);
|
|
||||||
|
|
||||||
// Determine status based on response time and status code
|
|
||||||
let status: 'healthy' | 'warning' | 'critical' = 'healthy';
|
|
||||||
if (response.status >= 500) {
|
|
||||||
status = 'critical';
|
|
||||||
} else if (response.status >= 400 || responseTime > 2000) {
|
|
||||||
status = 'warning';
|
|
||||||
} else if (responseTime > 1000) {
|
|
||||||
status = 'warning';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...endpoint,
|
|
||||||
status,
|
|
||||||
responseTime,
|
|
||||||
statusCode: response.status,
|
|
||||||
lastChecked: new Date(),
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
} catch (err: any) {
|
|
||||||
const endTime = performance.now();
|
|
||||||
const responseTime = Math.round(endTime - startTime);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...endpoint,
|
|
||||||
status: 'critical' as const,
|
|
||||||
responseTime,
|
|
||||||
statusCode: null,
|
|
||||||
lastChecked: new Date(),
|
|
||||||
error: err.name === 'AbortError' ? 'Timeout' : err.message || 'Network Error',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkAllEndpoints = async () => {
|
|
||||||
// Check all endpoints in parallel using ref to get latest state
|
|
||||||
const results = await Promise.all(
|
|
||||||
endpointsRef.current.map(endpoint => checkEndpoint(endpoint))
|
|
||||||
);
|
|
||||||
setEndpoints(results);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial check
|
|
||||||
checkAllEndpoints();
|
|
||||||
|
|
||||||
// Check every 5 seconds for real-time monitoring
|
|
||||||
const interval = setInterval(checkAllEndpoints, 5000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getStatusIcon = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'healthy':
|
|
||||||
return (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></div>
|
|
||||||
<span className="text-green-600 dark:text-green-400 text-xs font-medium">Online</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 'warning':
|
|
||||||
return (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="w-2 h-2 bg-yellow-500 rounded-full mr-2 animate-pulse"></div>
|
|
||||||
<span className="text-yellow-600 dark:text-yellow-400 text-xs font-medium">Slow</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 'critical':
|
|
||||||
return (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="w-2 h-2 bg-red-500 rounded-full mr-2"></div>
|
|
||||||
<span className="text-red-600 dark:text-red-400 text-xs font-medium">Down</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="w-2 h-2 bg-gray-400 rounded-full mr-2 animate-pulse"></div>
|
|
||||||
<span className="text-gray-600 dark:text-gray-400 text-xs font-medium">Checking...</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getResponseTimeColor = (responseTime: number | null) => {
|
|
||||||
if (responseTime === null) return 'text-gray-500 dark:text-gray-400';
|
|
||||||
if (responseTime < 500) return 'text-green-600 dark:text-green-400';
|
|
||||||
if (responseTime < 1000) return 'text-yellow-600 dark:text-yellow-400';
|
|
||||||
if (responseTime < 2000) return 'text-orange-600 dark:text-orange-400';
|
|
||||||
return 'text-red-600 dark:text-red-400';
|
|
||||||
};
|
|
||||||
|
|
||||||
const overallStatus = endpoints.every(e => e.status === 'healthy')
|
|
||||||
? 'healthy'
|
|
||||||
: endpoints.some(e => e.status === 'critical')
|
|
||||||
? 'critical'
|
|
||||||
: 'warning';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ComponentCard
|
|
||||||
title="API Monitoring"
|
|
||||||
desc="Real-time API endpoint health and response time monitoring"
|
|
||||||
>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{/* Overall Status */}
|
|
||||||
<div className="flex items-center justify-between pb-4 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<div>
|
|
||||||
<h4 className="text-sm font-semibold text-gray-800 dark:text-gray-200">Overall API Status</h4>
|
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
||||||
{endpoints.filter(e => e.status === 'healthy').length} of {endpoints.length} endpoints healthy
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className={`text-lg font-bold ${getStatusColor(overallStatus)}`}>
|
|
||||||
{overallStatus === 'healthy' ? '✓ All Systems Operational' :
|
|
||||||
overallStatus === 'warning' ? '⚠ Some Issues Detected' :
|
|
||||||
'✗ Critical Issues'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Endpoints List */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
{endpoints.map((endpoint, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-gray-700"
|
|
||||||
>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center justify-between mb-1">
|
|
||||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200 truncate">
|
|
||||||
{endpoint.name}
|
|
||||||
</span>
|
|
||||||
{getStatusIcon(endpoint.status)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4 text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
<span className="truncate font-mono">{endpoint.endpoint}</span>
|
|
||||||
{endpoint.responseTime !== null && (
|
|
||||||
<span className={`font-semibold ${getResponseTimeColor(endpoint.responseTime)}`}>
|
|
||||||
{endpoint.responseTime}ms
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{endpoint.statusCode && (
|
|
||||||
<span className={`font-semibold ${
|
|
||||||
endpoint.statusCode >= 200 && endpoint.statusCode < 300
|
|
||||||
? 'text-green-600 dark:text-green-400'
|
|
||||||
: endpoint.statusCode >= 400 && endpoint.statusCode < 500
|
|
||||||
? 'text-yellow-600 dark:text-yellow-400'
|
|
||||||
: 'text-red-600 dark:text-red-400'
|
|
||||||
}`}>
|
|
||||||
{endpoint.statusCode}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{endpoint.error && (
|
|
||||||
<div className="text-xs text-red-600 dark:text-red-400 mt-1">
|
|
||||||
{endpoint.error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{endpoint.lastChecked && (
|
|
||||||
<div className="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
||||||
Last checked: {endpoint.lastChecked.toLocaleTimeString()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Refresh Indicator */}
|
|
||||||
<div className="text-center text-xs text-gray-400 dark:text-gray-500 pt-2 border-t border-gray-200 dark:border-gray-700">
|
|
||||||
<span className="inline-flex items-center">
|
|
||||||
<span className="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></span>
|
|
||||||
Auto-refreshing every 5 seconds
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ComponentCard>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user