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="metrics" element={<MetricsExplorer />} />
|
||||||
<Route path="alert-center" element={<AlertCenter />} />
|
<Route path="alert-center" element={<AlertCenter />} />
|
||||||
<Route path="trace" element={<TraceManager />} />
|
<Route path="trace" element={<TraceManager />} />
|
||||||
<Route path="grafana" element={<GrafanaDashboard />} />
|
<Route path="dashboards" element={<GrafanaDashboard />} />
|
||||||
<Route path="alerts" element={<AlertManager />} />
|
<Route path="alerts" element={<AlertManager />} />
|
||||||
<Route path="logs" element={<AlertManager />} />
|
<Route path="logs" element={<AlertManager />} />
|
||||||
<Route path="approval" element={<ApprovalFlowManager />} />
|
<Route path="approval" element={<ApprovalFlowManager />} />
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const menuItems: MenuItem[] = [
|
|||||||
children: [
|
children: [
|
||||||
{ id: 'monitor-center', name: '资源监控', icon: Activity, path: '/monitor-center' },
|
{ id: 'monitor-center', name: '资源监控', icon: Activity, path: '/monitor-center' },
|
||||||
{ id: 'metrics', name: '指标监控', icon: Cpu, path: '/metrics' },
|
{ 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: 'trace', name: '链路追踪', icon: GitBranch, path: '/trace' },
|
||||||
{ id: 'alert-center', name: '告警服务', icon: Bell, path: '/alert-center' },
|
{ id: 'alert-center', name: '告警服务', icon: Bell, path: '/alert-center' },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import {
|
import {
|
||||||
Monitor, Settings, Plus, Edit, Trash2, Copy, Eye, Download, RefreshCw,
|
Monitor, Settings, Plus, Edit, Trash2, Copy, Eye, Download, RefreshCw,
|
||||||
Maximize2, Minimize2, Save, X, ChevronDown, BarChart3, LineChart, PieChart,
|
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
|
TrendingDown, Clock, Calendar, Filter, Search
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -23,6 +23,7 @@ import { toast } from 'sonner';
|
|||||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||||
import { dashboardApi } from '@/services/api';
|
import { dashboardApi } from '@/services/api';
|
||||||
import { GrafanaPanelContent } from './GrafanaPanelContent';
|
import { GrafanaPanelContent } from './GrafanaPanelContent';
|
||||||
|
import { http } from '@/services/http';
|
||||||
|
|
||||||
interface Dashboard {
|
interface Dashboard {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -107,6 +108,38 @@ const GrafanaDashboard: React.FC = () => {
|
|||||||
url: ''
|
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(() => {
|
useEffect(() => {
|
||||||
fetchDashboards();
|
fetchDashboards();
|
||||||
fetchDatasources();
|
fetchDatasources();
|
||||||
@@ -253,6 +286,62 @@ const GrafanaDashboard: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`space-y-4 ${fullscreen ? 'fixed inset-0 z-50 bg-background p-4' : ''}`}>
|
<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 justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|||||||
Reference in new issue
Block a user