import { useState, useEffect } from 'react'; import type { ServerConfig } from './types'; import { loadServers, saveServers, removeServer, exportServersToClipboard, importServersFromClipboard } from './utils/storage'; import { useServerMonitor } from './hooks/useServerMonitor'; import { ServerCard } from './components/ServerCard/ServerCard'; import { ServerDetail } from './components/ServerDetail/ServerDetail'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core'; import type { DragEndEvent } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, rectSortingStrategy, } from '@dnd-kit/sortable'; import './App.css'; const formatServerUrl = (url: string) => { let formatted = url.trim(); // Fix common typo .op -> .top based on user requirement if (formatted.endsWith('.op')) { formatted = formatted.slice(0, -3) + '.top'; } if (formatted && !/^https?:\/\//i.test(formatted)) { return `http://${formatted}`; } return formatted; }; function App() { const [servers, setServers] = useState([]); const [showAddForm, setShowAddForm] = useState(false); const [selectedServerId, setSelectedServerId] = useState(null); const [newServerForm, setNewServerForm] = useState({ name: '', url: '' }); const [showHeader, setShowHeader] = useState(true); const statuses = useServerMonitor(servers, 2000); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); useEffect(() => { const loaded = loadServers(); setServers(loaded); }, []); const handleUrlBlur = () => { const formatted = formatServerUrl(newServerForm.url); if (formatted !== newServerForm.url) { setNewServerForm(prev => ({ ...prev, url: formatted })); } }; const handleAddServer = () => { if (!newServerForm.name || !newServerForm.url) { alert('请填写服务器名称和地址'); return; } const formattedUrl = formatServerUrl(newServerForm.url); const newServer: ServerConfig = { id: Date.now().toString(), name: newServerForm.name, url: formattedUrl, enabled: true, }; const updated = [...servers, newServer]; setServers(updated); saveServers(updated); setNewServerForm({ name: '', url: '' }); setShowAddForm(false); }; const handleRemoveServer = (serverId: string) => { if (confirm('确定要移除这个服务器吗?')) { const updated = servers.filter(s => s.id !== serverId); setServers(updated); removeServer(serverId); } }; const handleShowDetail = (serverId: string) => { setSelectedServerId(serverId); }; const handleExportServers = async () => { try { await exportServersToClipboard(); alert('服务器配置已复制到剪贴板!'); } catch (error) { alert(error instanceof Error ? error.message : '导出失败'); } }; const handleImportServers = async () => { if (!confirm('导入服务器配置将添加到现有服务器列表中,是否继续?')) { return; } try { const importedServers = await importServersFromClipboard(); const updated = [...servers, ...importedServers]; setServers(updated); saveServers(updated); alert(`成功导入 ${importedServers.length} 个服务器配置!`); } catch (error) { alert(error instanceof Error ? error.message : '导入失败'); } }; const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { setServers((items) => { const oldIndex = items.findIndex((item) => item.id === active.id); const newIndex = items.findIndex((item) => item.id === over.id); const newOrder = arrayMove(items, oldIndex, newIndex); saveServers(newOrder); return newOrder; }); } }; const selectedStatus = selectedServerId ? statuses[selectedServerId] : null; const selectedServer = servers.find(s => s.id === selectedServerId); return (
{!showHeader && ( )} {showHeader && (

萌芽监控面板 萌芽监控面板

)} {showAddForm && (
setNewServerForm({ ...newServerForm, name: e.target.value })} /> setNewServerForm({ ...newServerForm, url: e.target.value })} onBlur={handleUrlBlur} />
)}
{servers.length === 0 ? (

还没有添加任何服务器

点击右上角的"添加服务器"按钮开始使用

) : ( s.id)} strategy={rectSortingStrategy}> {servers.map((server) => { const status = statuses[server.id]; // Calculate storage usage (max of all mounts) const storageUsage = status?.metrics?.storage?.reduce((max, s) => Math.max(max, s.usedPercent), 0) || 0; return ( ); })} )}
{selectedStatus?.metrics && selectedServer && ( setSelectedServerId(null)} /> )}
); } export default App;