Initial commit

This commit is contained in:
2025-06-22 00:01:22 +03:00
parent fd70166cf6
commit 033b80bfad
153 changed files with 26874 additions and 1 deletions

View File

@@ -0,0 +1,213 @@
"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>
)
}