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:
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}
|
||||
|
||||
Reference in new issue
Block a user