From e4958feb1b0e73f9880df7109bcedbc5c9148aff Mon Sep 17 00:00:00 2001 From: attacker Date: Mon, 15 Jun 2026 02:18:10 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20AlertCenter=E5=B4=A9=E6=BA=83=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E2=80=94=E2=80=94=E5=AD=97=E6=AE=B5=E5=90=8D=E6=98=A0?= =?UTF-8?q?=E5=B0=84+notify=5Fchannels=E5=AE=89=E5=85=A8=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AlertRule接口与后端对齐(id:number/notify_channels:string) - 告警事件数据映射(rule_name→title, message→description, target→source) - notify_channels安全解析(JSON.parse+try-catch替代直接.map) - labels/annotations字符串转对象安全处理 - Header tab栏移除(侧边栏导航足够) - SQL审计API改为真实后端调用 --- frontend/src/components/layout/Header.tsx | 35 +-------- .../monitor/alert-center/AlertCenter.tsx | 71 ++++++++++++++----- frontend/src/services/api.ts | 40 +++++++++-- 3 files changed, 87 insertions(+), 59 deletions(-) diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx index abcf701..7208056 100644 --- a/frontend/src/components/layout/Header.tsx +++ b/frontend/src/components/layout/Header.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { cn } from '@/lib/utils'; -import { useThemeStore, useTabStore, useNotificationStore, useUserStore } from '@/store'; +import { useThemeStore, useNotificationStore, useUserStore } from '@/store'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; @@ -17,7 +17,6 @@ import { Search, Bell, Menu, - X, Settings, User, LogOut, @@ -33,7 +32,6 @@ import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme'; export function Header() { const { sidebarCollapsed, toggleSidebar } = useThemeStore(); - const { tabs, activeTab, removeTab, setActiveTab } = useTabStore(); const { notifications, unreadCount, markAsRead, markAllAsRead } = useNotificationStore(); const { user, logout } = useUserStore(); const { theme, setTheme } = useTheme(); @@ -226,36 +224,7 @@ export function Header() { - {/* Tabs */} - {tabs.length > 0 && ( -
- {tabs.map((tab) => ( -
setActiveTab(tab.id)} - > - {tab.title} - {tab.closable && ( - - )} -
- ))} -
- )} + {/* Tabs 已移除 — 侧边栏导航已足够,tab 栏多余且遮挡内容 */} ); } diff --git a/frontend/src/components/monitor/alert-center/AlertCenter.tsx b/frontend/src/components/monitor/alert-center/AlertCenter.tsx index 31ce844..a2f53a0 100644 --- a/frontend/src/components/monitor/alert-center/AlertCenter.tsx +++ b/frontend/src/components/monitor/alert-center/AlertCenter.tsx @@ -46,18 +46,23 @@ export interface Alert { annotations: Record; } +// Logan.Li — 字段名与后端 alert.go AlertRule 对齐 interface AlertRule { - id: string; + id: number; name: string; description: string; enabled: boolean; - severity: 'critical' | 'warning' | 'info'; - query: string; - duration: string; - threshold: number; - labels: string[]; - notificationChannels: string[]; - lastEvaluated: string; + severity: string; + type?: string; + source?: string; + expression?: string; + duration?: number; + threshold?: number; + labels?: string; + annotations?: string; + notify_channels?: string; + created_at?: string; + updated_at?: string; } interface NotificationChannel { @@ -103,7 +108,24 @@ const AlertCenter: React.FC = () => { setLoading(true); try { const res = await alertCenterApi.getAlerts(); - const data = res.list || []; + const raw = (res.list || []) as any[]; + // Logan.Li — 后端字段映射到前端 Alert 接口 + const data: Alert[] = raw.map(a => ({ + id: String(a.id), + title: a.rule_name || a.title || `告警 #${a.id}`, + description: a.message || a.description || '', + severity: a.severity || 'info', + status: a.status || 'firing', + source: a.target || a.source || '', + service: a.service || '', + metric: a.metric || '', + value: a.value || 0, + threshold: a.threshold || 0, + startedAt: a.started_at || a.startedAt || a.created_at || new Date().toISOString(), + resolvedAt: a.resolved_at || a.resolvedAt, + labels: typeof a.labels === 'string' ? (() => { try { return JSON.parse(a.labels || '{}'); } catch { return {}; } })() : (a.labels || {}), + annotations: typeof a.annotations === 'string' ? (() => { try { return JSON.parse(a.annotations || '{}'); } catch { return {}; } })() : (a.annotations || {}), + })); setAlerts(data); setStats({ firing: data.filter((a: Alert) => a.status === 'firing').length, @@ -171,7 +193,7 @@ const AlertCenter: React.FC = () => { const toggleRule = async (ruleId: string, enabled: boolean) => { try { await alertCenterApi.toggleRule(ruleId); - setRules(rules.map(r => r.id === ruleId ? { ...r, enabled } : r)); + setRules(rules.map(r => String(r.id) === ruleId ? { ...r, enabled } : r)); toast.success('规则状态已更新'); } catch (error) { toast.error('更新失败'); @@ -531,20 +553,31 @@ const AlertCenter: React.FC = () => {

{rule.description}

- {rule.query} - 持续 {rule.duration} - 阈值: {rule.threshold} -
-
- {rule.notificationChannels.map(ch => ( - {ch} - ))} + {rule.expression && ( + {rule.expression} + )} + {rule.duration ? 持续 {rule.duration}秒 : null}
+ {(() => { + let channels: string[] = []; + try { + const raw = rule.notify_channels; + if (raw) channels = Array.isArray(raw) ? raw : JSON.parse(raw); + } catch { /* ignore */ } + if (!channels.length) return null; + return ( +
+ {channels.map(ch => ( + {ch} + ))} +
+ ); + })()}
toggleRule(rule.id, checked)} + onCheckedChange={(checked) => toggleRule(String(rule.id), checked)} />