"use client" import React, { useEffect, useState, useCallback } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { useToast } from "@/components/ui/use-toast" import { useRouter } from "next/navigation" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { Shield, Ban, Clock, Trash2, RefreshCw } from "lucide-react" interface BannedIP { ip: string ban_end_time: string remaining_seconds: number } interface SecurityStats { banned_ips_count: number error_records_count: number config: { ErrorThreshold: number WindowMinutes: number BanDurationMinutes: number CleanupIntervalMinutes: number } } interface IPStatus { ip: string banned: boolean ban_end_time?: string remaining_seconds?: number } export default function SecurityPage() { const [bannedIPs, setBannedIPs] = useState([]) const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const [checkingIP, setCheckingIP] = useState("") const [ipStatus, setIPStatus] = useState(null) const [unbanning, setUnbanning] = useState(null) const { toast } = useToast() const router = useRouter() const fetchData = useCallback(async () => { try { const token = localStorage.getItem("token") if (!token) { router.push("/login") return } const [bannedResponse, statsResponse] = await Promise.all([ fetch("/admin/api/security/banned-ips", { headers: { 'Authorization': `Bearer ${token}` } }), fetch("/admin/api/security/stats", { headers: { 'Authorization': `Bearer ${token}` } }) ]) if (bannedResponse.status === 401 || statsResponse.status === 401) { localStorage.removeItem("token") router.push("/login") return } if (bannedResponse.ok) { const bannedData = await bannedResponse.json() setBannedIPs(bannedData.banned_ips || []) } if (statsResponse.ok) { const statsData = await statsResponse.json() setStats(statsData) } } catch (error) { console.error("获取安全数据失败:", error) toast({ title: "错误", description: "获取安全数据失败", variant: "destructive", }) } finally { setLoading(false) setRefreshing(false) } }, [router, toast]) useEffect(() => { fetchData() // 每30秒自动刷新一次数据 const interval = setInterval(fetchData, 30000) return () => clearInterval(interval) }, [fetchData]) const handleRefresh = () => { setRefreshing(true) fetchData() } const checkIPStatus = async () => { if (!checkingIP.trim()) return try { const token = localStorage.getItem("token") if (!token) { router.push("/login") return } const response = await fetch(`/admin/api/security/check-ip?ip=${encodeURIComponent(checkingIP)}`, { headers: { 'Authorization': `Bearer ${token}` } }) if (response.status === 401) { localStorage.removeItem("token") router.push("/login") return } if (response.ok) { const data = await response.json() setIPStatus(data) } else { throw new Error("检查IP状态失败") } } catch { toast({ title: "错误", description: "检查IP状态失败", variant: "destructive", }) } } const unbanIP = async (ip: string) => { try { const token = localStorage.getItem("token") if (!token) { router.push("/login") return } const response = await fetch("/admin/api/security/unban", { method: "POST", headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ ip }) }) if (response.status === 401) { localStorage.removeItem("token") router.push("/login") return } if (response.ok) { const data = await response.json() if (data.success) { toast({ title: "成功", description: `IP ${ip} 已解封`, }) fetchData() // 刷新数据 } else { toast({ title: "提示", description: data.message, }) } } else { throw new Error("解封IP失败") } } catch { toast({ title: "错误", description: "解封IP失败", variant: "destructive", }) } finally { setUnbanning(null) } } const formatTime = (seconds: number) => { if (seconds <= 0) return "已过期" const minutes = Math.floor(seconds / 60) const remainingSeconds = seconds % 60 if (minutes > 0) { return `${minutes}分${remainingSeconds}秒` } return `${remainingSeconds}秒` } if (loading) { return (
加载中...
正在获取安全数据
) } return (
安全管理 {stats && (
{stats.banned_ips_count}
被封禁IP
{stats.error_records_count}
错误记录
错误阈值
{stats.config.ErrorThreshold}次/{stats.config.WindowMinutes}分钟
封禁时长
{stats.config.BanDurationMinutes}分钟
)}
setCheckingIP(e.target.value)} />
{ipStatus && (
IP: {ipStatus.ip}
{ipStatus.banned ? '已封禁' : '正常'}
{ipStatus.banned && ipStatus.remaining_seconds && ipStatus.remaining_seconds > 0 && (
剩余时间: {formatTime(ipStatus.remaining_seconds)}
)}
)}
被封禁的IP列表 {bannedIPs.length === 0 ? (
当前没有被封禁的IP
) : ( IP地址 封禁结束时间 剩余时间 操作 {bannedIPs.map((bannedIP) => ( {bannedIP.ip} {bannedIP.ban_end_time} {formatTime(bannedIP.remaining_seconds)} ))}
)}
!open && setUnbanning(null)}> 确认解封 确定要解封IP地址 “{unbanning}” 吗? 取消 unbanning && unbanIP(unbanning)}> 确认解封
) }