feat: 新增日志中心、功能优化与代码重构
- 新增 log_datasource.go / log_proxy.go / user_api_token.go 后端控制器 - 新增 log_datasource.go model - 新增 monitor/log-center.ts API 模块 - 新增 LogCenter 前端组件及页面 - 重构 deployment 相关组件 (HPDA/UpdateImageModal/bundle-yaml 等) - 新增 ResetPasswordDialog / code-snippet-block / copy-button 等 UI 组件 - 重构多个 k8s 相关组件 (TopologyView/ResourceVisualEditor/PodFileBrowser 等) - 更新多个 API 和工具函数 (auth.ts/monitor/index.ts/request.ts 等) - 更新静态资源 (index.html + 新 JS/CSS bundle) - 删除废弃静态资源文件
This commit is contained in:
1 parent
5ca0e6d83a
commit
8d46e8f352
197 files changed
+5014
-815
No files matched your search
@@ -0,0 +1,118 @@
|
||||
// LogAlertsPanel — 日志告警规则列表 // Logan.Li
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Plus, Pencil, Trash2, RefreshCw, Loader2 } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { alertApi } from '@/api';
|
||||
import { AlertRuleDialog } from '@/components/monitor/AlertRuleDialog';
|
||||
|
||||
export function LogAlertsPanel() {
|
||||
const { toast } = useToast();
|
||||
const confirm = useConfirm();
|
||||
const [rules, setRules] = useState<any[]>([]);
|
||||
const [channels, setChannels] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<any>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [rulesRes, chRes] = await Promise.all([
|
||||
alertApi.getRules(),
|
||||
alertApi.getNotifyChannels(),
|
||||
]);
|
||||
const all = (rulesRes as any)?.list || (Array.isArray(rulesRes) ? rulesRes : []);
|
||||
setRules(all.filter((r: any) => r.type === 'log' || r.source === 'loki' || r.source === 'elasticsearch'));
|
||||
setChannels((chRes as any)?.list || []);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
|
||||
const handleDelete = async (rule: any) => {
|
||||
const ok = await confirm({ title: '删除规则', description: `删除「${rule.name}」?`, variant: 'destructive', confirmText: '删除' });
|
||||
if (!ok) return;
|
||||
try {
|
||||
await alertApi.deleteRule(rule.id);
|
||||
toast({ title: '已删除' });
|
||||
load();
|
||||
} catch (e: any) {
|
||||
toast({ title: '删除失败', description: e?.message || String(e), variant: 'destructive' });
|
||||
}
|
||||
};
|
||||
|
||||
const severityColor: Record<string, string> = {
|
||||
critical: 'bg-red-500/15 text-red-400',
|
||||
warning: 'bg-amber-500/15 text-amber-400',
|
||||
info: 'bg-blue-500/15 text-blue-400',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-[var(--dg-text-muted)]">仅保存告警策略(LogQL / Lucene),日志数据仍在外部 Loki / ES</p>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={load} disabled={loading}>
|
||||
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />刷新
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => { setEditing(null); setDialogOpen(true); }}>
|
||||
<Plus className="h-4 w-4 mr-1" />新建
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center py-16"><Loader2 className="h-6 w-6 animate-spin text-[var(--dg-text-muted)]" /></div>
|
||||
) : rules.length === 0 ? (
|
||||
<Card className="dg-card">
|
||||
<CardContent className="py-12 text-center text-[var(--dg-text-muted)] text-sm">
|
||||
暂无日志告警。在 Loki/ES 查询页点击右下角「叮当」可从当前查询快速创建。
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{rules.map(rule => (
|
||||
<Card key={rule.id} className="dg-card">
|
||||
<CardContent className="py-3 px-4 flex flex-wrap items-center gap-3">
|
||||
<div className="flex-1 min-w-[200px]">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-medium text-[var(--dg-text-primary)]">{rule.name}</span>
|
||||
<Badge variant="outline" className="text-[10px] border-[var(--dg-border)]">{rule.source || 'log'}</Badge>
|
||||
<Badge className={`text-[10px] ${severityColor[rule.severity] || ''}`}>{rule.severity}</Badge>
|
||||
{!rule.enabled && rule.enabled !== undefined && (
|
||||
<Badge variant="outline" className="text-[10px] text-[var(--dg-text-muted)]">已禁用</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[11px] font-mono text-[var(--dg-text-muted)] mt-1 truncate" title={rule.expression}>{rule.expression}</p>
|
||||
</div>
|
||||
<div className="flex gap-1 shrink-0">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => { setEditing(rule); setDialogOpen(true); }}>
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-red-400" onClick={() => handleDelete(rule)}>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AlertRuleDialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
editingRule={editing}
|
||||
notifyChannels={channels}
|
||||
onSaved={load}
|
||||
defaultType="log"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user