'use client'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Plus, RefreshCw, Route, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import type { ProxyRouteItem } from '@/lib/services/openflare'; import { ProxyRouteService } from '@/lib/services/openflare'; import { getRouteDomainNames, getRouteDomainsLabel, getRoutePrimaryDomain, getUpstreamSummary, } from './components/helpers'; import { ProxyRouteCreateSheet } from './components/proxy-route-create-sheet'; export function ProxyRoutesPageClient() { const router = useRouter(); const [routes, setRoutes] = useState([]); const [loading, setLoading] = useState(true); const [keyword, setKeyword] = useState(''); const [isCreateOpen, setIsCreateOpen] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); const fetchRoutes = useCallback(async () => { setLoading(true); try { const data = await ProxyRouteService.list(); setRoutes(data); } catch (error) { toast.error('规则列表加载失败', { description: error instanceof Error ? error.message : '未知错误', }); } finally { setLoading(false); } }, []); useEffect(() => { void fetchRoutes(); }, [fetchRoutes]); const filteredRoutes = useMemo(() => { const normalizedKeyword = keyword.trim().toLowerCase(); if (!normalizedKeyword) { return routes; } return routes.filter((route) => { const haystack = [ route.site_name, getRoutePrimaryDomain(route), ...getRouteDomainNames(route), route.origin_url, ] .join(' ') .toLowerCase(); return haystack.includes(normalizedKeyword); }); }, [keyword, routes]); const handleDelete = async () => { if (!deleteTarget) { return; } setDeleting(true); try { await ProxyRouteService.deleteById(deleteTarget.id); toast.success('网站已删除'); setDeleteTarget(null); await fetchRoutes(); } catch (error) { toast.error('删除失败', { description: error instanceof Error ? error.message : '未知错误', }); } finally { setDeleting(false); } }; return (

规则配置

规则列表 setKeyword(event.target.value)} placeholder='搜索站点、域名或上游...' className='h-8 max-w-sm text-xs' />
{loading ? (
{Array.from({ length: 5 }).map((_, index) => ( ))}
) : filteredRoutes.length === 0 ? (

{routes.length === 0 ? '暂无网站' : '没有匹配结果'}

{routes.length === 0 ? '先创建一个网站,再进入配置子页面继续补齐 HTTPS、缓存和限流。' : '试试调整搜索词,或者清空筛选条件。'}

) : ( 站点名称 域名 状态 上游 操作 {filteredRoutes.map((route) => ( {route.site_name} {getRoutePrimaryDomain(route) || getRouteDomainsLabel(route)} {route.enabled ? '已启用' : '已停用'} {getUpstreamSummary(route)}
))}
)}
{ toast.success('网站已创建'); void fetchRoutes(); router.push(`/proxy-routes/detail?id=${route.id}§ion=domains`); }} /> { if (!open) { setDeleteTarget(null); } }} > 确认删除网站 {deleteTarget ? `确认删除网站 ${deleteTarget.site_name} 吗?这会删除该站点下的全部域名与配置。` : null} 取消 { event.preventDefault(); void handleDelete(); }} > {deleting ? '删除中...' : '删除'}
); }