import React, { useState, useEffect } from 'react'; import { Monitor, Settings, Plus, Edit, Trash2, Copy, Eye, Download, RefreshCw, Maximize2, Minimize2, Save, X, ChevronDown, BarChart3, LineChart, PieChart, Activity, Cpu, Database, Server, Globe, Zap, AlertTriangle, TrendingUp, TrendingDown, Clock, Calendar, Filter, Search, GripVertical, Move } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Separator } from '@/components/ui/separator'; import { toast } from 'sonner'; import { dashboardApi } from '@/services/api'; interface Dashboard { id: string; name: string; description: string; datasource: 'prometheus' | 'zabbix' | 'skywalking' | 'custom'; datasourceUrl: string; panels: Panel[]; createdAt: string; updatedAt: string; createdBy: string; isDefault: boolean; } interface Panel { id: string; title: string; type: 'graph' | 'stat' | 'gauge' | 'table' | 'pie' | 'heatmap' | 'text'; query: string; datasource: string; width: number; height: number; x: number; y: number; options: PanelOptions; } interface PanelOptions { unit?: string; min?: number; max?: number; thresholds?: Threshold[]; legend?: boolean; tooltip?: boolean; refreshInterval?: number; } interface Threshold { value: number; color: string; name: string; } interface Datasource { id: string; name: string; type: 'prometheus' | 'zabbix' | 'skywalking' | 'influxdb' | 'elasticsearch'; url: string; status: 'connected' | 'disconnected' | 'error'; } const GrafanaDashboard: React.FC = () => { const [dashboards, setDashboards] = useState([]); const [datasources, setDatasources] = useState([]); const [activeDashboard, setActiveDashboard] = useState(null); const [loading, setLoading] = useState(false); const [editMode, setEditMode] = useState(false); const [fullscreen, setFullscreen] = useState(false); const [createOpen, setCreateOpen] = useState(false); const [addPanelOpen, setAddPanelOpen] = useState(false); const [datasourceOpen, setDatasourceOpen] = useState(false); const [newDashboard, setNewDashboard] = useState({ name: '', description: '', datasource: 'prometheus' as const, datasourceUrl: '' }); const [newPanel, setNewPanel] = useState({ title: '', type: 'graph' as const, query: '', width: 6, height: 4 }); const [newDatasource, setNewDatasource] = useState({ name: '', type: 'prometheus' as const, url: '' }); useEffect(() => { fetchDashboards(); fetchDatasources(); }, []); const fetchDashboards = async () => { setLoading(true); try { const res = await dashboardApi.getDashboards(); setDashboards(res.data || []); if (res.data?.length > 0 && !activeDashboard) { setActiveDashboard(res.data[0]); } } catch (error) { toast.error('获取仪表盘失败'); } finally { setLoading(false); } }; const fetchDatasources = async () => { try { const res = await dashboardApi.getDatasources(); setDatasources(res.data || []); } catch (error) { toast.error('获取数据源失败'); } }; const handleCreateDashboard = async () => { if (!newDashboard.name) { toast.error('请输入仪表盘名称'); return; } try { await dashboardApi.createDashboard({ ...newDashboard, panels: [] }); toast.success('仪表盘创建成功'); setCreateOpen(false); setNewDashboard({ name: '', description: '', datasource: 'prometheus', datasourceUrl: '' }); fetchDashboards(); } catch (error) { toast.error('创建失败'); } }; const handleAddPanel = async () => { if (!newPanel.title || !newPanel.query || !activeDashboard) { toast.error('请填写完整信息'); return; } try { const panel: Panel = { id: Date.now().toString(), ...newPanel, datasource: activeDashboard.datasource, x: 0, y: 0, options: { unit: '', legend: true, tooltip: true, refreshInterval: 30 } }; await dashboardApi.updateDashboard(activeDashboard.id, { ...activeDashboard, panels: [...activeDashboard.panels, panel] }); toast.success('面板添加成功'); setAddPanelOpen(false); setNewPanel({ title: '', type: 'graph', query: '', width: 6, height: 4 }); fetchDashboards(); } catch (error) { toast.error('添加失败'); } }; const handleAddDatasource = async () => { if (!newDatasource.name || !newDatasource.url) { toast.error('请填写完整信息'); return; } try { await dashboardApi.createDatasource(newDatasource); toast.success('数据源添加成功'); setDatasourceOpen(false); setNewDatasource({ name: '', type: 'prometheus', url: '' }); fetchDatasources(); } catch (error) { toast.error('添加失败'); } }; const handleDeleteDashboard = async (id: string) => { if (!confirm('确定删除此仪表盘吗?')) return; try { await dashboardApi.deleteDashboard(id); toast.success('删除成功'); if (activeDashboard?.id === id) { setActiveDashboard(null); } fetchDashboards(); } catch (error) { toast.error('删除失败'); } }; const getDatasourceIcon = (type: string) => { switch (type) { case 'prometheus': return ; case 'zabbix': return ; case 'skywalking': return ; case 'influxdb': return ; default: return ; } }; const getPanelIcon = (type: string) => { switch (type) { case 'graph': return ; case 'stat': return ; case 'gauge': return ; case 'pie': return ; case 'table': return ; default: return ; } }; const getDatasourceStatusBadge = (status: string) => { switch (status) { case 'connected': return 已连接; case 'disconnected': return 已断开; case 'error': return 错误; default: return 未知; } }; // 模拟图表数据 const generateChartData = (panel: Panel) => { const dataPoints = 24; const data = []; for (let i = 0; i < dataPoints; i++) { data.push({ time: new Date(Date.now() - (dataPoints - i) * 3600000).toLocaleTimeString(), value: Math.random() * 100 }); } return data; }; // 渲染面板内容 const renderPanelContent = (panel: Panel) => { const data = generateChartData(panel); switch (panel.type) { case 'stat': return (
{Math.floor(Math.random() * 1000)} {panel.options?.unit || 'requests/s'}
+12.5%
); case 'gauge': return (
{Math.floor(Math.random() * 100)}%
CPU使用率
); case 'graph': default: return (
{data.map((d, i) => (
))}
{data[0].time} {data[data.length - 1].time}
); } }; return (
{/* 顶部工具栏 */}
{activeDashboard && (
更新于 {activeDashboard.updatedAt}
)}
{activeDashboard && ( <> )}
{/* 仪表盘内容 */} {activeDashboard ? (
{/* 仪表盘信息 */}

{activeDashboard.name}

{activeDashboard.description}

{getDatasourceIcon(activeDashboard.datasource)} {activeDashboard.datasource}
{/* 面板网格 */}
{activeDashboard.panels.map((panel) => (
{getPanelIcon(panel.type)} {panel.title} {editMode && (
)}
{renderPanelContent(panel)}
))} {activeDashboard.panels.length === 0 && (

暂无面板,点击"添加面板"开始创建

)}
) : (

请选择一个仪表盘或创建新的仪表盘

)} {/* 新建仪表盘对话框 */} 新建仪表盘 创建一个新的监控仪表盘
setNewDashboard({ ...newDashboard, name: e.target.value })} placeholder="仪表盘名称" />
setNewDashboard({ ...newDashboard, description: e.target.value })} placeholder="仪表盘描述" />
setNewDashboard({ ...newDashboard, datasourceUrl: e.target.value })} placeholder="http://localhost:9090" />
{/* 添加面板对话框 */} 添加面板 添加一个新的监控面板
setNewPanel({ ...newPanel, title: e.target.value })} placeholder="面板标题" />