Files
ai-showcase-platform/app/admin/connection-monitor/page.tsx

372 lines
12 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 React, { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Loader2, Database, Trash2, Settings, Activity, Clock, Users } from 'lucide-react';
interface ConnectionStats {
totalConnections: number;
idleConnections: number;
oldConnections: number;
maxIdleTime: number;
maxConnectionAge: number;
connections: Array<{
createdAt: string;
lastUsed: string;
idleTime: number;
age: number;
userId?: string;
sessionId?: string;
}>;
}
export default function ConnectionMonitorPage() {
const [stats, setStats] = useState<ConnectionStats | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
const [maxIdleTime, setMaxIdleTime] = useState<number>(300000); // 5分鐘
const [maxConnectionAge, setMaxConnectionAge] = useState<number>(1800000); // 30分鐘
// 獲取連線統計
const fetchStats = async () => {
try {
setLoading(true);
const response = await fetch('/api/connection-monitor?action=stats');
const data = await response.json();
if (data.success) {
setStats(data.data);
setMessage('統計更新成功');
setError('');
} else {
setError(data.error || '獲取統計失敗');
}
} catch (err) {
setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤'));
} finally {
setLoading(false);
}
};
// 強制清理連線
const forceCleanup = async () => {
if (!confirm('確定要強制清理所有連線嗎?這可能會影響正在進行的操作!')) {
return;
}
try {
setLoading(true);
const response = await fetch('/api/connection-monitor?action=cleanup');
const data = await response.json();
if (data.success) {
setMessage('連線清理完成');
setError('');
await fetchStats(); // 重新獲取統計
} else {
setError(data.error || '清理失敗');
}
} catch (err) {
setError('清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤'));
} finally {
setLoading(false);
}
};
// 測試智能連線
const testConnection = async () => {
try {
setLoading(true);
const response = await fetch('/api/connection-monitor?action=test');
const data = await response.json();
if (data.success) {
setMessage('智能連線測試成功');
setError('');
await fetchStats(); // 重新獲取統計
} else {
setError(data.error || '測試失敗');
}
} catch (err) {
setError('測試錯誤: ' + (err instanceof Error ? err.message : '未知錯誤'));
} finally {
setLoading(false);
}
};
// 更新清理配置
const updateConfig = async () => {
try {
setLoading(true);
const response = await fetch('/api/connection-monitor', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'config',
maxIdleTime,
maxConnectionAge
}),
});
const data = await response.json();
if (data.success) {
setMessage('清理配置更新成功');
setError('');
await fetchStats(); // 重新獲取統計
} else {
setError(data.error || '配置更新失敗');
}
} catch (err) {
setError('配置更新錯誤: ' + (err instanceof Error ? err.message : '未知錯誤'));
} finally {
setLoading(false);
}
};
// 格式化時間
const formatTime = (ms: number) => {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) return `${hours}h ${minutes % 60}m`;
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
return `${seconds}s`;
};
// 組件載入時獲取統計
useEffect(() => {
fetchStats();
// 每30秒自動更新統計
const interval = setInterval(fetchStats, 30000);
return () => clearInterval(interval);
}, []);
return (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold"></h1>
<p className="text-muted-foreground">
</p>
</div>
<Button
onClick={fetchStats}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Activity className="h-4 w-4" />}
</Button>
</div>
{/* 統計概覽 */}
{stats && (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Database className="h-5 w-5 text-blue-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">{stats.totalConnections}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Clock className="h-5 w-5 text-yellow-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">{stats.idleConnections}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-red-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">{stats.oldConnections}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Activity className="h-5 w-5 text-green-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">
{stats.idleConnections + stats.oldConnections === 0 ? '良好' : '需清理'}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
)}
{/* 操作按鈕 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="h-5 w-5" />
</CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Button
onClick={testConnection}
disabled={loading}
variant="outline"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />}
</Button>
<Button
onClick={forceCleanup}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
</Button>
<Button
onClick={updateConfig}
disabled={loading}
variant="secondary"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Settings className="h-4 w-4" />}
</Button>
</div>
{/* 配置設定 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4 border-t">
<div className="space-y-2">
<Label htmlFor="maxIdleTime"> ()</Label>
<Input
id="maxIdleTime"
type="number"
value={maxIdleTime}
onChange={(e) => setMaxIdleTime(Number(e.target.value))}
placeholder="300000 (5分鐘)"
/>
</div>
<div className="space-y-2">
<Label htmlFor="maxConnectionAge"> ()</Label>
<Input
id="maxConnectionAge"
type="number"
value={maxConnectionAge}
onChange={(e) => setMaxConnectionAge(Number(e.target.value))}
placeholder="1800000 (30分鐘)"
/>
</div>
</div>
</CardContent>
</Card>
{/* 連線詳情 */}
{stats && stats.connections.length > 0 && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
{stats.connections.map((conn, index) => (
<div key={index} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-4">
<Badge variant="outline">#{index + 1}</Badge>
<div>
<p className="text-sm font-medium">
: {new Date(conn.createdAt).toLocaleString()}
</p>
<p className="text-sm text-muted-foreground">
使: {new Date(conn.lastUsed).toLocaleString()}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Badge variant={conn.idleTime > stats.maxIdleTime ? "destructive" : "default"}>
: {formatTime(conn.idleTime)}
</Badge>
<Badge variant={conn.age > stats.maxConnectionAge ? "destructive" : "default"}>
: {formatTime(conn.age)}
</Badge>
{conn.userId && (
<Badge variant="secondary">: {conn.userId}</Badge>
)}
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 訊息顯示 */}
{message && (
<Alert>
<Activity className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<Database className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 說明資訊 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong>:</strong> 30</p>
<p> <strong>:</strong> 使</p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
</CardContent>
</Card>
</div>
);
}