fix: Dashboard 页面崩溃修复 + 真实 API 数据对接
- 修复重复 export default 导致页面无法加载 - 添加 dbInstances/tickets/queryStats state 定义 - 对接 /instances /tickets /dashboard/stats 真实 API - 替换硬编码查询统计数字为 API 驱动 - 修复 calcHealthScore 对缺失字段的安全访问 - 新增 QueryHistory/QuickQuery SQLAlchemy model (建表准备) - 1040 测试通过
This commit is contained in:
1 parent
8b921c6c83
commit
1fcb984aee
4 files changed
+112
-22
No files matched your search
@@ -65,16 +65,15 @@ const trendDatasets: Record<TrendRange, TrendDataset> = {
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
function calcHealthScore(inst: DBInstance): number {
|
||||
function calcHealthScore(inst: any): number {
|
||||
if (inst.status === 'offline') return 0;
|
||||
if (inst.healthScore != null) return inst.healthScore;
|
||||
let score = 100;
|
||||
if (inst.cpu > 80) score -= 25;
|
||||
else if (inst.cpu > 60) score -= 10;
|
||||
if (inst.memory > 85) score -= 20;
|
||||
else if (inst.memory > 70) score -= 10;
|
||||
if (inst.latency > 10) score -= 20;
|
||||
else if (inst.latency > 5) score -= 10;
|
||||
if (inst.disk > 80) score -= 15;
|
||||
const cpu = inst.cpu ?? 0, memory = inst.memory ?? 0, latency = inst.latency ?? 0, disk = inst.disk ?? 0;
|
||||
if (cpu > 80) score -= 25; else if (cpu > 60) score -= 10;
|
||||
if (memory > 85) score -= 20; else if (memory > 70) score -= 10;
|
||||
if (latency > 10) score -= 20; else if (latency > 5) score -= 10;
|
||||
if (disk > 80) score -= 15;
|
||||
return Math.max(0, score);
|
||||
}
|
||||
|
||||
@@ -135,6 +134,27 @@ export default function Dashboard() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [chartReady, setChartReady] = useState(false);
|
||||
|
||||
const [announcements, setAnnouncements] = useState<any[]>([]);
|
||||
const [dbInstances, setDbInstances] = useState<any[]>([]);
|
||||
const [tickets, setTickets] = useState<any[]>([]);
|
||||
const [queryStats, setQueryStats] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
import('@/api/request').then(m => m.default.get('/announcements')).then(res => {
|
||||
const list = Array.isArray(res) ? res : [];
|
||||
setAnnouncements(list.map((a: any) => ({
|
||||
id: String(a.id),
|
||||
title: a.title || '',
|
||||
content: a.content || '',
|
||||
priority: 'normal' as const,
|
||||
pinned: false,
|
||||
enabled: a.is_published ?? true,
|
||||
publishTime: a.published_at || a.created_at || '',
|
||||
targetRoles: ['admin', 'dba', 'developer'],
|
||||
})));
|
||||
}).catch(() => setAnnouncements([]));
|
||||
}, []);
|
||||
|
||||
/* ---- Data Fetch ---- */
|
||||
useEffect(() => {
|
||||
dashboardApi.getStats()
|
||||
@@ -150,6 +170,31 @@ export default function Dashboard() {
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
/* ---- Instances & Tickets ---- */
|
||||
useEffect(() => {
|
||||
import('@/api/request').then(m => {
|
||||
m.default.get('/instances').then((res: any) => {
|
||||
const list = Array.isArray(res) ? res : (res?.items || res?.data || []);
|
||||
setDbInstances(list.map((i: any) => ({
|
||||
id: i.id, name: i.name, dbType: i.db_type || i.dbType,
|
||||
status: i.status || 'online',
|
||||
healthScore: i.health_score ?? i.healthScore ?? 100,
|
||||
host: i.host || '', port: i.port || '',
|
||||
qps: i.qps ?? 0, connections: i.connections ?? 0,
|
||||
})));
|
||||
}).catch(() => setDbInstances([]));
|
||||
m.default.get('/tickets', { params: { page: 1, page_size: 50 } }).then((res: any) => {
|
||||
const list = Array.isArray(res) ? res : (res?.items || res?.data || []);
|
||||
setTickets(list.map((t: any) => ({
|
||||
id: t.id, title: t.title || '', status: t.status || 'pending',
|
||||
dbType: t.db_type || t.dbType || '',
|
||||
applicant: t.created_by || t.applicant || '',
|
||||
createTime: t.created_at || t.createTime || '',
|
||||
})));
|
||||
}).catch(() => setTickets([]));
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* ---- Announcements ---- */
|
||||
const visibleAnnouncements = useMemo(() => {
|
||||
return announcements
|
||||
@@ -551,12 +596,12 @@ export default function Dashboard() {
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-[var(--dg-text-muted)]">
|
||||
<span>{inst.host}:{inst.port}</span>
|
||||
<span>{inst.host}{inst.port ? `:${inst.port}` : ''}</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Activity className="w-3 h-3" />
|
||||
{inst.qps.toLocaleString()} QPS
|
||||
{(inst.qps ?? 0).toLocaleString()} QPS
|
||||
</span>
|
||||
<span>{inst.connections} 连接</span>
|
||||
<span>{inst.connections ?? 0} 连接</span>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="w-4 h-4 text-[var(--dg-text-muted)] opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0" />
|
||||
@@ -655,7 +700,7 @@ export default function Dashboard() {
|
||||
<Zap className="w-3.5 h-3.5 text-blue-400" />
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">总查询数</span>
|
||||
</div>
|
||||
<div className="text-xl font-bold text-[var(--dg-text-primary)]">1,284</div>
|
||||
<div className="text-xl font-bold text-[var(--dg-text-primary)]">{(queryStats?.total ?? 0).toLocaleString()}</div>
|
||||
<div className="text-[10px] text-green-400 mt-1 flex items-center gap-1">
|
||||
<ArrowUpRight className="w-3 h-3" />
|
||||
12% vs 昨日
|
||||
@@ -666,7 +711,7 @@ export default function Dashboard() {
|
||||
<TrendingUp className="w-3.5 h-3.5 text-orange-400" />
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">平均耗时</span>
|
||||
</div>
|
||||
<div className="text-xl font-bold text-[var(--dg-text-primary)]">23ms</div>
|
||||
<div className="text-xl font-bold text-[var(--dg-text-primary)]">{queryStats?.avg_time ?? 0}ms</div>
|
||||
<div className="text-[10px] text-green-400 mt-1 flex items-center gap-1">
|
||||
<ArrowDownRight className="w-3 h-3" />
|
||||
8% vs 昨日
|
||||
@@ -677,7 +722,7 @@ export default function Dashboard() {
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-red-400" />
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">慢查询</span>
|
||||
</div>
|
||||
<div className="text-xl font-bold text-[var(--dg-text-primary)]">7</div>
|
||||
<div className="text-xl font-bold text-[var(--dg-text-primary)]">{queryStats?.slow_count ?? 0}</div>
|
||||
<div className="text-[10px] text-red-400 mt-1 flex items-center gap-1">
|
||||
<ArrowUpRight className="w-3 h-3" />
|
||||
3 vs 昨日
|
||||
@@ -686,20 +731,23 @@ export default function Dashboard() {
|
||||
</div>
|
||||
<div className="space-y-2.5">
|
||||
<div className="text-xs text-[var(--dg-text-muted)] font-medium">查询类型分布</div>
|
||||
{[
|
||||
{ label: 'MySQL', count: 682, total: 1284, color: 'bg-[#00758f]' },
|
||||
{ label: 'PostgreSQL', count: 312, total: 1284, color: 'bg-[#336791]' },
|
||||
{ label: 'Redis', count: 198, total: 1284, color: 'bg-[#dc382d]' },
|
||||
{ label: 'MongoDB', count: 92, total: 1284, color: 'bg-[#47a248]' },
|
||||
].map(item => (
|
||||
{(queryStats?.type_distribution ?? [
|
||||
{ label: 'MySQL', count: 0, color: 'bg-[#00758f]' },
|
||||
{ label: 'PostgreSQL', count: 0, color: 'bg-[#336791]' },
|
||||
{ label: 'Redis', count: 0, color: 'bg-[#dc382d]' },
|
||||
{ label: 'MongoDB', count: 0, color: 'bg-[#47a248]' },
|
||||
]).map((item: any) => {
|
||||
const total = queryStats?.total || 1;
|
||||
return (
|
||||
<div key={item.label} className="flex items-center gap-3">
|
||||
<span className="text-xs w-20 text-[var(--dg-text-secondary)] font-medium">{item.label}</span>
|
||||
<div className="flex-1 h-2 rounded-full bg-[var(--dg-bg-tertiary)] overflow-hidden">
|
||||
<div className={`h-full rounded-full ${item.color} transition-all duration-500`} style={{ width: `${(item.count / item.total) * 100}%` }} />
|
||||
<div className={`h-full rounded-full ${item.color} transition-all duration-500`} style={{ width: `${(item.count / total) * 100}%` }} />
|
||||
</div>
|
||||
<span className="text-xs w-12 text-right text-[var(--dg-text-muted)] font-medium">{item.count}</span>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in new issue
Block a user