import { useState, useEffect, useCallback } from 'react' import { useNavigate } from '@tanstack/react-router' import { fetchWithAuth } from '@/lib/fetch-with-auth' import { Card } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { ScrollArea } from '@/components/ui/scroll-area' import { Switch } from '@/components/ui/switch' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { ArrowLeft, Plus, Pencil, Trash2, Loader2, AlertTriangle, ChevronUp, ChevronDown } from 'lucide-react' import { useToast } from '@/hooks/use-toast' interface MirrorConfig { id: string name: string raw_prefix: string clone_prefix: string enabled: boolean priority: number created_at?: string updated_at?: string } export function PluginMirrorsPage() { const navigate = useNavigate() const { toast } = useToast() const [mirrors, setMirrors] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [editingMirror, setEditingMirror] = useState(null) const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) // 表单状态 const [formData, setFormData] = useState({ id: '', name: '', raw_prefix: '', clone_prefix: '', enabled: true, priority: 1 }) // 加载镜像源列表 const loadMirrors = useCallback(async () => { try { setLoading(true) setError(null) const response = await fetchWithAuth('/api/webui/plugins/mirrors') if (!response.ok) { throw new Error('获取镜像源列表失败') } const data = await response.json() setMirrors(data.mirrors || []) } catch (err) { const errorMessage = err instanceof Error ? err.message : '加载镜像源失败' setError(errorMessage) toast({ title: '加载失败', description: errorMessage, variant: 'destructive', }) } finally { setLoading(false) } }, [toast]) useEffect(() => { loadMirrors() }, [loadMirrors]) // 添加镜像源 const handleAddMirror = async () => { try { const response = await fetchWithAuth('/api/webui/plugins/mirrors', { method: 'POST', body: JSON.stringify(formData) }) if (!response.ok) { const errorData = await response.json() throw new Error(errorData.detail || '添加镜像源失败') } toast({ title: '添加成功', description: '镜像源已添加' }) setIsAddDialogOpen(false) setFormData({ id: '', name: '', raw_prefix: '', clone_prefix: '', enabled: true, priority: 1 }) loadMirrors() } catch (err) { toast({ title: '添加失败', description: err instanceof Error ? err.message : '未知错误', variant: 'destructive' }) } } // 更新镜像源 const handleUpdateMirror = async () => { if (!editingMirror) return try { const response = await fetchWithAuth(`/api/webui/plugins/mirrors/${editingMirror.id}`, { method: 'PUT', body: JSON.stringify({ name: formData.name, raw_prefix: formData.raw_prefix, clone_prefix: formData.clone_prefix, enabled: formData.enabled, priority: formData.priority }) }) if (!response.ok) { throw new Error('更新镜像源失败') } toast({ title: '更新成功', description: '镜像源已更新' }) setIsEditDialogOpen(false) setEditingMirror(null) loadMirrors() } catch (err) { toast({ title: '更新失败', description: err instanceof Error ? err.message : '未知错误', variant: 'destructive' }) } } // 删除镜像源 const handleDeleteMirror = async (id: string) => { if (!confirm('确定要删除这个镜像源吗?')) return try { const response = await fetchWithAuth(`/api/webui/plugins/mirrors/${id}`, { method: 'DELETE' }) if (!response.ok) { throw new Error('删除镜像源失败') } toast({ title: '删除成功', description: '镜像源已删除' }) loadMirrors() } catch (err) { toast({ title: '删除失败', description: err instanceof Error ? err.message : '未知错误', variant: 'destructive' }) } } // 切换启用状态 const handleToggleEnabled = async (mirror: MirrorConfig) => { try { const response = await fetchWithAuth(`/api/webui/plugins/mirrors/${mirror.id}`, { method: 'PUT', body: JSON.stringify({ enabled: !mirror.enabled }) }) if (!response.ok) { throw new Error('更新状态失败') } loadMirrors() } catch (err) { toast({ title: '更新失败', description: err instanceof Error ? err.message : '未知错误', variant: 'destructive' }) } } // 打开编辑对话框 const openEditDialog = (mirror: MirrorConfig) => { setEditingMirror(mirror) setFormData({ id: mirror.id, name: mirror.name, raw_prefix: mirror.raw_prefix, clone_prefix: mirror.clone_prefix, enabled: mirror.enabled, priority: mirror.priority }) setIsEditDialogOpen(true) } // 调整优先级 const adjustPriority = async (mirror: MirrorConfig, direction: 'up' | 'down') => { const newPriority = direction === 'up' ? mirror.priority - 1 : mirror.priority + 1 if (newPriority < 1) return try { const response = await fetchWithAuth(`/api/webui/plugins/mirrors/${mirror.id}`, { method: 'PUT', body: JSON.stringify({ priority: newPriority }) }) if (!response.ok) { throw new Error('更新优先级失败') } loadMirrors() } catch (err) { toast({ title: '更新失败', description: err instanceof Error ? err.message : '未知错误', variant: 'destructive' }) } } return (
{/* 标题栏 */}

镜像源配置

管理 Git 克隆和文件下载的镜像源

{/* 加载状态 */} {loading ? (
) : error ? (

加载失败

{error}

) : ( {/* 桌面端表格 */}
状态 名称 ID 优先级 操作 {mirrors.map((mirror) => ( handleToggleEnabled(mirror)} />
{mirror.name}
Raw: {mirror.raw_prefix}
{mirror.id}
{mirror.priority}
))}
{/* 移动端卡片 */}
{mirrors.map((mirror) => (

{mirror.name}

{mirror.enabled && ( 启用 )}
{mirror.id}
handleToggleEnabled(mirror)} />
Raw: {mirror.raw_prefix}
优先级: {mirror.priority}
))}
)} {/* 添加镜像源对话框 */} 添加镜像源 添加新的 Git 镜像源配置
setFormData({ ...formData, id: e.target.value })} />
setFormData({ ...formData, name: e.target.value })} />
setFormData({ ...formData, raw_prefix: e.target.value })} />
setFormData({ ...formData, clone_prefix: e.target.value })} />
setFormData({ ...formData, priority: parseInt(e.target.value) || 1 })} />

数字越小优先级越高

setFormData({ ...formData, enabled: checked })} />
{/* 编辑镜像源对话框 */} 编辑镜像源 修改镜像源配置
setFormData({ ...formData, name: e.target.value })} />
setFormData({ ...formData, raw_prefix: e.target.value })} />
setFormData({ ...formData, clone_prefix: e.target.value })} />
setFormData({ ...formData, priority: parseInt(e.target.value) || 1 })} />

数字越小优先级越高

setFormData({ ...formData, enabled: checked })} />
) }