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:
Logan committed 2026-06-26 23:18:05 +08:00
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>
);
}