refactor(frontend): P3 — K8s 组件拆分、AlertCenter Query、permission utils

- 拆分 DeploymentView / DeploymentDetail 至子目录(<700 行红线)
- AlertCenter 接入 useAlerts / useAgentAlertRules / useAlertMutations
- 新增 utils/permission.ts,usePermission 复用纯函数
- useAlerts 增强分页统计与 Agent 规则 hooks
This commit is contained in:
Logan.Li committed 2026-06-26 10:51:15 +08:00
1 parent 37c657987b
commit 521bfd356f
24 files changed
+1454 -1221

No files matched your search

@@ -1,5 +1,5 @@
// 告警中心主组件 // Logan.Li
import React, { useState, useEffect } from 'react';
// 告警中心主组件 — TanStack Query 编排 // Logan.Li
import React, { useState, useEffect, useMemo } from 'react';
import {
AlertTriangle, CheckCircle, Search, RefreshCw,
Eye, Check, VolumeX, Plus, Rocket,
@@ -16,9 +16,10 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Switch } from '@/components/ui/switch';
import { toast } from 'sonner';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { alertCenterApi, agentApi, alertApi } from '@/api';
import { alertCenterApi, alertApi } from '@/api';
import { useAlerts, useAgentAlertRules, useAlertMutations } from '@/hooks/api';
import {
type Alert, type AlertRule, mapAlert, getSeverityBadge, getStatusBadge,
type Alert, type AlertRule, getSeverityBadge, getStatusBadge,
getChannelTypeLabel,
} from './alert-center-helpers';
import { AlertDetailDialog, AlertSilenceDialog } from './AlertCenterDetail';
@@ -30,9 +31,6 @@ import type { Contact, ContactGroup } from './alert-center-helpers';
const AlertCenter: React.FC = () => {
const _confirm = useConfirm();
const [alerts, setAlerts] = useState<Alert[]>([]);
const [rules, setRules] = useState<AlertRule[]>([]);
const [loading, setLoading] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [severityFilter, setSeverityFilter] = useState('all');
const [statusFilter, setStatusFilter] = useState('all');
@@ -40,11 +38,9 @@ const AlertCenter: React.FC = () => {
const [selectedAlert, setSelectedAlert] = useState<Alert | null>(null);
const [detailOpen, setDetailOpen] = useState(false);
const [silenceOpen, setSilenceOpen] = useState(false);
const [stats, setStats] = useState({ firing: 0, critical: 0, warning: 0, resolved: 0 });
// 规则弹框
const [ruleDialogOpen, setRuleDialogOpen] = useState(false);
const [editingRule, setEditingRule] = useState<any>(null);
const [quickSetupLoading, setQuickSetupLoading] = useState(false);
// 联系人 & 联系组
const [contacts, setContacts] = useState<Contact[]>([]);
const [contactGroups, setContactGroups] = useState<ContactGroup[]>([]);
@@ -55,67 +51,33 @@ const AlertCenter: React.FC = () => {
// 分页
const [page, setPage] = useState(1);
const [pageSize] = useState(10);
const [total, setTotal] = useState(0);
const alertParams = useMemo(() => ({
page,
page_size: pageSize,
severity: severityFilter,
status: statusFilter,
}), [page, pageSize, severityFilter, statusFilter]);
const { data: alertData, isLoading: loading, refetch: refetchAlerts } = useAlerts(alertParams);
const { data: rules = [], refetch: refetchRules } = useAgentAlertRules();
const {
ackMutation,
resolveMutation,
quickSetupMutation,
toggleAgentRuleMutation,
deleteAgentRuleMutation,
} = useAlertMutations();
const alerts = alertData?.list ?? [];
const total = alertData?.total ?? 0;
const stats = alertData?.stats ?? { firing: 0, critical: 0, warning: 0, resolved: 0 };
const totalPages = Math.max(1, Math.ceil(total / pageSize));
useEffect(() => {
fetchData();
if (activeTab === 'contacts') fetchContacts();
if (activeTab === 'groups') { fetchContacts(); fetchContactGroups(); }
const interval = setInterval(fetchData, 30000);
return () => clearInterval(interval);
}, [page, severityFilter, statusFilter, activeTab]);
const fetchData = async () => {
await Promise.all([fetchAlerts(), fetchRules()]);
};
// Logan.Li — 获取告警列表(分页)
const fetchAlerts = async () => {
setLoading(true);
try {
const res: Record<string, any> = await alertCenterApi.getAlerts({
page,
page_size: pageSize,
severity: severityFilter !== 'all' ? severityFilter : undefined,
status: statusFilter !== 'all' ? statusFilter : undefined,
});
const raw = (res.list || res.data?.list || []) as any[];
const data: Alert[] = raw.map(mapAlert);
setAlerts(data);
setTotal(res.total || res.data?.total || data.length);
setStats({
firing: data.filter(a => a.status === 'firing').length,
critical: data.filter(a => a.severity === 'critical' && a.status === 'firing').length,
warning: data.filter(a => a.severity === 'warning' && a.status === 'firing').length,
resolved: data.filter(a => a.status === 'resolved').length,
});
} catch {
toast.error('获取告警失败');
} finally {
setLoading(false);
}
};
const fetchRules = async () => {
try {
const res = await agentApi.listRules();
// agentApi.listRules returns {code, data: AgentRule[]} — map to AlertRule for UI
const raw = (Array.isArray(res) ? res : (res.list || [])) as any[];
setRules(raw.map(r => ({
id: r.id,
name: r.name,
description: `${r.metric} ${r.operator} ${r.threshold}%`,
enabled: r.enabled,
severity: 'warning',
type: r.metric,
hostname: r.hostname || '全部主机',
threshold: r.threshold,
})) as AlertRule[]);
} catch {
toast.error('获取告警规则失败');
}
};
}, [activeTab]);
const fetchContacts = async () => {
try {
@@ -133,9 +95,8 @@ const AlertCenter: React.FC = () => {
const handleAcknowledge = async (id: string) => {
try {
await alertCenterApi.acknowledge(id);
await ackMutation.mutateAsync(id);
toast.success('告警已确认');
fetchAlerts();
} catch {
toast.error('操作失败');
}
@@ -143,9 +104,8 @@ const AlertCenter: React.FC = () => {
const handleResolve = async (id: string) => {
try {
await alertCenterApi.resolve(id);
await resolveMutation.mutateAsync(id);
toast.success('告警已解决');
fetchAlerts();
} catch {
toast.error('操作失败');
}
@@ -169,7 +129,7 @@ const AlertCenter: React.FC = () => {
});
toast.success(`告警已静默 ${duration}`);
setSilenceOpen(false);
fetchAlerts();
refetchAlerts();
} catch {
toast.error('操作失败');
}
@@ -177,8 +137,7 @@ const AlertCenter: React.FC = () => {
const toggleRule = async (ruleId: string, enabled: boolean) => {
try {
await agentApi.updateRule(Number(ruleId), { enabled });
setRules(rules.map(r => String(r.id) === ruleId ? { ...r, enabled } : r));
await toggleAgentRuleMutation.mutateAsync({ id: Number(ruleId), enabled });
toast.success('规则状态已更新');
} catch {
toast.error('更新失败');
@@ -196,20 +155,18 @@ const AlertCenter: React.FC = () => {
confirmText: '确认创建',
});
if (!ok) return;
setQuickSetupLoading(true);
try {
const res = await alertCenterApi.quickSetup('ecs');
toast.success(typeof res === 'string' ? res : (res?.message || '默认告警规则创建成功'));
fetchRules();
const res = await quickSetupMutation.mutateAsync('ecs');
toast.success(typeof res === 'string' ? res : ((res as { message?: string })?.message || '默认告警规则创建成功'));
} catch {
toast.error('创建失败,请检查 Agent 主机是否在线');
} finally { setQuickSetupLoading(false); }
}
};
const handleDeleteRule = async (rule: AlertRule) => {
const ok = await _confirm({ title: '删除规则', description: `确定删除规则「${rule.name}」?`, confirmText: '删除', variant: 'destructive' });
if (!ok) return;
try { await agentApi.deleteRule(Number(rule.id)); toast.success('已删除'); fetchRules(); } catch { toast.error('删除失败'); }
try { await deleteAgentRuleMutation.mutateAsync(Number(rule.id)); toast.success('已删除'); } catch { toast.error('删除失败'); }
};
const handleFilterChange = (type: 'severity' | 'status', value: string) => {
@@ -277,7 +234,7 @@ const AlertCenter: React.FC = () => {
</div>
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<span>共 {total} 条</span>
<Button variant="outline" size="sm" onClick={fetchAlerts} className="h-7 text-xs px-2">
<Button variant="outline" size="sm" onClick={() => refetchAlerts()} className="h-7 text-xs px-2">
<RefreshCw className="w-3 h-3 mr-1" />刷新
</Button>
</div>
@@ -402,12 +359,12 @@ const AlertCenter: React.FC = () => {
<Button onClick={handleOpenAddRule}>
<Plus className="w-4 h-4 mr-1" />添加规则
</Button>
<Button variant="secondary" onClick={handleQuickSetup} disabled={quickSetupLoading}>
<Button variant="secondary" onClick={handleQuickSetup} disabled={quickSetupMutation.isPending}>
<Rocket className="w-4 h-4 mr-1" />
{quickSetupLoading ? '创建中…' : '一键报警'}
{quickSetupMutation.isPending ? '创建中…' : '一键报警'}
</Button>
</div>
<Button variant="outline" onClick={fetchRules}>
<Button variant="outline" onClick={() => refetchRules()}>
<RefreshCw className="w-4 h-4 mr-2" />
刷新
</Button>
@@ -615,7 +572,7 @@ const AlertCenter: React.FC = () => {
onOpenChange={handleRuleDialogClose}
editingRule={editingRule}
notifyChannels={[]}
onSaved={() => { handleRuleDialogClose(); fetchRules(); }}
onSaved={() => { handleRuleDialogClose(); refetchRules(); }}
/>
<ContactDialog
open={contactDialogOpen}