Files
mamad-app/components/database-monitor.tsx
2025-06-22 00:01:22 +03:00

214 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { RefreshCw, Database, Activity, AlertTriangle, CheckCircle } from "lucide-react"
interface PoolStats {
totalConnections: number
freeConnections: number
acquiringConnections: number
connectionLimit: number
}
interface HealthData {
status: string
responseTime?: number
poolStats?: PoolStats
recommendations?: string[]
error?: string
timestamp: string
}
export function DatabaseMonitor() {
const [healthData, setHealthData] = useState<HealthData | null>(null)
const [loading, setLoading] = useState(false)
const [autoRefresh, setAutoRefresh] = useState(false)
const fetchHealth = async () => {
setLoading(true)
try {
const response = await fetch("/api/admin/db-health")
const data = await response.json()
setHealthData(data)
} catch (error) {
console.error("Error fetching database health:", error)
setHealthData({
status: "error",
error: "Failed to fetch health data",
timestamp: new Date().toISOString(),
})
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchHealth()
}, [])
useEffect(() => {
let interval: NodeJS.Timeout
if (autoRefresh) {
interval = setInterval(fetchHealth, 5000) // Refresh every 5 seconds
}
return () => {
if (interval) clearInterval(interval)
}
}, [autoRefresh])
const getStatusIcon = (status: string) => {
switch (status) {
case "healthy":
return <CheckCircle className="h-5 w-5 text-green-500" />
case "unhealthy":
return <AlertTriangle className="h-5 w-5 text-red-500" />
default:
return <Activity className="h-5 w-5 text-yellow-500" />
}
}
const getUtilizationColor = (utilization: number) => {
if (utilization > 0.8) return "text-red-600"
if (utilization > 0.6) return "text-yellow-600"
return "text-green-600"
}
return (
<div className="space-y-4" dir="rtl">
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
מוניטור מסד נתונים
</CardTitle>
<div className="flex gap-2">
<Button
variant={autoRefresh ? "default" : "outline"}
size="sm"
onClick={() => setAutoRefresh(!autoRefresh)}
>
{autoRefresh ? "עצור רענון" : "רענון אוטומטי"}
</Button>
<Button variant="outline" size="sm" onClick={fetchHealth} disabled={loading}>
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
רענן
</Button>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
{healthData ? (
<>
{/* Status Overview */}
<div className="flex items-center gap-2 p-3 bg-gray-50 rounded">
{getStatusIcon(healthData.status)}
<span className="font-semibold">סטטוס: {healthData.status === "healthy" ? "תקין" : "לא תקין"}</span>
{healthData.responseTime && (
<span className="text-sm text-gray-600">({healthData.responseTime}ms)</span>
)}
</div>
{/* Pool Statistics */}
{healthData.poolStats && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-blue-50 p-3 rounded text-center">
<div className="text-2xl font-bold text-blue-600">{healthData.poolStats.totalConnections}</div>
<div className="text-sm text-gray-600">חיבורים פעילים</div>
</div>
<div className="bg-green-50 p-3 rounded text-center">
<div className="text-2xl font-bold text-green-600">{healthData.poolStats.freeConnections}</div>
<div className="text-sm text-gray-600">חיבורים זמינים</div>
</div>
<div className="bg-yellow-50 p-3 rounded text-center">
<div className="text-2xl font-bold text-yellow-600">
{healthData.poolStats.acquiringConnections}
</div>
<div className="text-sm text-gray-600">ממתינים לחיבור</div>
</div>
<div className="bg-purple-50 p-3 rounded text-center">
<div className="text-2xl font-bold text-purple-600">{healthData.poolStats.connectionLimit}</div>
<div className="text-sm text-gray-600">מגבלת חיבורים</div>
</div>
</div>
)}
{/* Utilization Bar */}
{healthData.poolStats && (
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>ניצולת Pool</span>
<span
className={getUtilizationColor(
(healthData.poolStats.totalConnections - healthData.poolStats.freeConnections) /
healthData.poolStats.connectionLimit,
)}
>
{Math.round(
((healthData.poolStats.totalConnections - healthData.poolStats.freeConnections) /
healthData.poolStats.connectionLimit) *
100,
)}
%
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{
width: `${Math.min(
((healthData.poolStats.totalConnections - healthData.poolStats.freeConnections) /
healthData.poolStats.connectionLimit) *
100,
100,
)}%`,
}}
/>
</div>
</div>
)}
{/* Recommendations */}
{healthData.recommendations && healthData.recommendations.length > 0 && (
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<div className="space-y-1">
<strong>המלצות:</strong>
<ul className="list-disc list-inside space-y-1">
{healthData.recommendations.map((rec, index) => (
<li key={index} className="text-sm">
{rec}
</li>
))}
</ul>
</div>
</AlertDescription>
</Alert>
)}
{/* Error Display */}
{healthData.error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{healthData.error}</AlertDescription>
</Alert>
)}
{/* Timestamp */}
<div className="text-xs text-gray-500 text-center">
עודכן: {new Date(healthData.timestamp).toLocaleString("he-IL")}
</div>
</>
) : (
<div className="text-center py-8 text-gray-500">טוען נתוני בריאות מסד הנתונים...</div>
)}
</CardContent>
</Card>
</div>
)
}