feat: 监控大屏 /grafana→/dashboards + Agent主机Top10默认面板
- 路由: /grafana → /dashboards (Sidebar + App.tsx) - GrafanaDashboard 新增默认面板: Agent 主机监控 Top 10 - CPU/Memory/Disk 使用率三列排行 - 进度条颜色: >85%红 >60%黄 <60%绿 - 🥇🥈🥉 emoji 前三名 - 30s 自动刷新 - Prometheus/Zabbix/SkyWalking 需配置数据源后才可用
This commit is contained in:
1 parent
737f00169f
commit
2bf3e7c308
3 files changed
+92
-3
No files matched your search
@@ -73,7 +73,7 @@ function App() {
|
||||
<Route path="metrics" element={<MetricsExplorer />} />
|
||||
<Route path="alert-center" element={<AlertCenter />} />
|
||||
<Route path="trace" element={<TraceManager />} />
|
||||
<Route path="grafana" element={<GrafanaDashboard />} />
|
||||
<Route path="dashboards" element={<GrafanaDashboard />} />
|
||||
<Route path="alerts" element={<AlertManager />} />
|
||||
<Route path="logs" element={<AlertManager />} />
|
||||
<Route path="approval" element={<ApprovalFlowManager />} />
|
||||
|
||||
@@ -88,7 +88,7 @@ const menuItems: MenuItem[] = [
|
||||
children: [
|
||||
{ id: 'monitor-center', name: '资源监控', icon: Activity, path: '/monitor-center' },
|
||||
{ id: 'metrics', name: '指标监控', icon: Cpu, path: '/metrics' },
|
||||
{ id: 'grafana', name: '监控大屏', icon: Monitor, path: '/grafana' },
|
||||
{ id: 'grafana', name: '监控大屏', icon: Monitor, path: '/dashboards' },
|
||||
{ id: 'trace', name: '链路追踪', icon: GitBranch, path: '/trace' },
|
||||
{ id: 'alert-center', name: '告警服务', icon: Bell, path: '/alert-center' },
|
||||
],
|
||||
|
||||
@@ -2,7 +2,7 @@ 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,
|
||||
Activity, Cpu, Database, Server, Globe, Zap, AlertTriangle, TrendingUp, HardDrive,
|
||||
TrendingDown, Clock, Calendar, Filter, Search
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -23,6 +23,7 @@ import { toast } from 'sonner';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { dashboardApi } from '@/services/api';
|
||||
import { GrafanaPanelContent } from './GrafanaPanelContent';
|
||||
import { http } from '@/services/http';
|
||||
|
||||
interface Dashboard {
|
||||
id: string;
|
||||
@@ -107,6 +108,38 @@ const GrafanaDashboard: React.FC = () => {
|
||||
url: ''
|
||||
});
|
||||
|
||||
// Agent 主机监控 — 默认面板
|
||||
const [hostTop, setHostTop] = useState<any[]>([]);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const [cpu, mem, disk] = await Promise.all([
|
||||
http.get('/metrics/top?metric=cpu&limit=10'),
|
||||
http.get('/metrics/top?metric=memory&limit=10'),
|
||||
http.get('/metrics/top?metric=disk&limit=10'),
|
||||
]);
|
||||
setHostTop([
|
||||
{ name: 'CPU', data: Array.isArray(cpu) ? cpu : [], key: 'cpu_usage', unit: '%', icon: 'cpu' },
|
||||
{ name: '内存', data: Array.isArray(mem) ? mem : [], key: 'memory_usage', unit: '%', icon: 'mem' },
|
||||
{ name: '磁盘', data: Array.isArray(disk) ? disk : [], key: 'disk_usage', unit: '%', icon: 'disk' },
|
||||
]);
|
||||
} catch { /* silent */ }
|
||||
})();
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
const cpu = await http.get('/metrics/top?metric=cpu&limit=10');
|
||||
const mem = await http.get('/metrics/top?metric=memory&limit=10');
|
||||
const disk = await http.get('/metrics/top?metric=disk&limit=10');
|
||||
setHostTop([
|
||||
{ name: 'CPU', data: Array.isArray(cpu) ? cpu : [], key: 'cpu_usage', unit: '%', icon: 'cpu' },
|
||||
{ name: '内存', data: Array.isArray(mem) ? mem : [], key: 'memory_usage', unit: '%', icon: 'mem' },
|
||||
{ name: '磁盘', data: Array.isArray(disk) ? disk : [], key: 'disk_usage', unit: '%', icon: 'disk' },
|
||||
]);
|
||||
} catch { /* silent */ }
|
||||
}, 30000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDashboards();
|
||||
fetchDatasources();
|
||||
@@ -253,6 +286,62 @@ const GrafanaDashboard: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className={`space-y-4 ${fullscreen ? 'fixed inset-0 z-50 bg-background p-4' : ''}`}>
|
||||
{/* ===== 默认:Agent 主机监控面板 ===== */}
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-emerald-500/20">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5 text-emerald-400" />
|
||||
<CardTitle className="text-base text-[var(--dg-text-primary)]">Agent 主机监控</CardTitle>
|
||||
<Badge variant="outline" className="text-xs bg-emerald-500/10 text-emerald-400 border-emerald-500/30">默认</Badge>
|
||||
<span className="text-xs text-[var(--dg-text-muted)] ml-auto">每 30s 刷新</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
{hostTop.length === 0 ? (
|
||||
<div className="text-center py-8 text-[var(--dg-text-muted)]">
|
||||
<Server className="h-8 w-8 mx-auto mb-2 text-slate-600" />
|
||||
<p className="text-sm">暂无 Agent 上报数据</p>
|
||||
<p className="text-xs mt-1">请确保至少一台主机安装了 AIOps Agent 并正常上报</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
{hostTop.map((group: any) => (
|
||||
<div key={group.name} className="bg-[var(--dg-bg-primary)] rounded-lg p-3 border border-[var(--dg-border)]">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{group.icon === 'cpu' && <Cpu className="h-4 w-4 text-red-400" />}
|
||||
{group.icon === 'mem' && <Server className="h-4 w-4 text-amber-400" />}
|
||||
{group.icon === 'disk' && <HardDrive className="h-4 w-4 text-orange-400" />}
|
||||
<span className="text-xs font-medium text-[var(--dg-text-primary)]">{group.name} Top 10</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{group.data.slice(0, 10).map((h: any, i: number) => {
|
||||
const val = Number(h[group.key] || 0);
|
||||
const barPct = Math.min(val, 100);
|
||||
const barColor = val > 85 ? 'bg-red-500' : val > 60 ? 'bg-amber-500' : 'bg-emerald-500';
|
||||
const dotColor = i < 3 ? (i === 0 ? 'text-red-400' : i === 1 ? 'text-amber-400' : 'text-orange-400') : 'text-slate-500';
|
||||
return (
|
||||
<div key={i} className="flex items-center gap-1.5 text-[11px]">
|
||||
<span className={`w-4 text-right font-mono font-bold ${dotColor}`}>
|
||||
{i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `${i + 1}`}
|
||||
</span>
|
||||
<span className="w-24 truncate text-[var(--dg-text-secondary)]" title={h.hostname}>
|
||||
{h.hostname}
|
||||
</span>
|
||||
<div className="flex-1 h-1 rounded-full bg-[var(--dg-bg-tertiary)] overflow-hidden">
|
||||
<div className={`h-full rounded-full ${barColor}`} style={{ width: `${barPct}%` }} />
|
||||
</div>
|
||||
<span className="w-12 text-right font-mono text-[var(--dg-text-primary)]">{val.toFixed(1)}{group.unit}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 顶部工具栏 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
|
||||
Reference in new issue
Block a user