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)}
/>