Files
aiops/frontend/src/components/monitor/log-center/LogClusterPanel.tsx
T
attacker 72bfb0e12d fix(i18n): 回退到稳定的 32% 覆盖率状态 (tsc + Vite 构建通过)
批量转换脚本引入的语法错误已回退,保留稳定的 i18n 转换:
- 翻译词典: 22 namespaces, 3600+ keys
- 已接入: 145+ 文件 (Dashboard/Login/K8s 主页面/Monitor/System/Deploy)
- 语言切换器在 Header 中可用 (🇨🇳/🇺🇸)
- tsc 零错误, Vite 构建成功
2026-07-03 02:02:29 +08:00

109 lines
4.7 KiB
TypeScript

// LogClusterPanel.tsx — 日志聚类面板 // Logan.Li
import { useState } from 'react';
import { ChevronDown, ChevronRight, Copy, Check } from 'lucide-react';
import type { LogCluster } from './types-v3';
interface Props {
clusters: LogCluster[];
}
function PatternHighlight({ pattern }: { pattern: string }) {
// 将 * 高亮显示 // Logan.Li
const parts = pattern.split('*');
return (
<span className="text-xs font-mono">
{parts.map((part, i) => (
<span key={i}>
{part}
{i < parts.length - 1 && <span className="px-1 rounded" style={{ background: 'rgba(245,158,11,0.2)', color: '#f59e0b', fontWeight: 600 }}>*</span>}
</span>
))}
</span>
);
}
export function LogClusterPanel({ clusters }: Props) {
const [expanded, setExpanded] = useState<Record<number, boolean>>({});
const [copied, setCopied] = useState<number | null>(null);
const toggleExpand = (i: number) => setExpanded(prev => ({ ...prev, [i]: !prev[i] }));
const copySample = (i: number, text: string) => {
navigator.clipboard.writeText(text);
setCopied(i);
setTimeout(() => setCopied(null), 1500);
};
const total = clusters.reduce((sum, c) => sum + c.count, 0);
return (
<div className="border rounded-lg overflow-hidden" style={{ borderColor: 'var(--dg-border)' }}>
{/* 头部 */}
<div className="flex items-center justify-between px-4 py-2" style={{ background: 'var(--dg-bg-tertiary)', borderBottom: '1px solid var(--dg-border)' }}>
<span className="text-xs" style={{ color: 'var(--dg-text-muted)' }}>共 {clusters.length} 个聚类模式,总计 {total.toLocaleString()} 条日志</span>
<span className="text-[10px]" style={{ color: 'var(--dg-text-muted)' }}>基于日志相似度自动聚类</span>
</div>
{/* 聚类列表 */}
<div>
{clusters.map((cluster, i) => (
<div key={i} style={{ borderBottom: '1px solid var(--dg-border)' }}>
<div
className="flex items-center gap-3 px-4 py-2.5 cursor-pointer hover:opacity-90 transition-colors"
style={{ background: 'var(--dg-bg-secondary)' }}
onClick={() => toggleExpand(i)}
>
<span className="flex-shrink-0" style={{ color: 'var(--dg-text-muted)' }}>
{expanded[i] ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
</span>
{/* 模式 */}
<div className="flex-1 min-w-0">
<PatternHighlight pattern={cluster.pattern} />
</div>
{/* 数量 */}
<span className="text-xs font-medium flex-shrink-0" style={{ color: 'var(--dg-text-primary)' }}>
{cluster.count.toLocaleString()}
</span>
{/* 百分比 */}
<div className="flex-shrink-0 w-[80px]">
<div className="flex items-center gap-1.5">
<div className="flex-1 h-1.5 rounded-full overflow-hidden" style={{ background: 'var(--dg-bg-tertiary)' }}>
<div className="h-full rounded-full" style={{ width: `${cluster.percentage}%`, background: 'var(--dg-blue)' }} />
</div>
<span className="text-[10px] flex-shrink-0" style={{ color: 'var(--dg-text-muted)' }}>{cluster.percentage.toFixed(1)}%</span>
</div>
</div>
{/* 复制 */}
<button
onClick={e => { e.stopPropagation(); copySample(i, cluster.sample); }}
className="flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
style={{ color: 'var(--dg-text-muted)' }}
>
{copied === i ? <Check className="h-3 w-3 dg-text-green" /> : <Copy className="h-3 w-3" />}
</button>
</div>
{/* 展开详情 */}
{expanded[i] && (
<div className="px-4 py-3 animate-fade-in" style={{ background: 'var(--dg-bg-primary)', paddingLeft: '48px' }}>
<p className="text-[10px] mb-1" style={{ color: 'var(--dg-text-muted)' }}>样例日志:</p>
<div className="p-2 rounded text-[11px] font-mono break-all" style={{ background: 'var(--dg-bg-tertiary)', color: 'var(--dg-text-secondary)' }}>
{cluster.sample}
</div>
<div className="flex gap-4 mt-2 text-[10px]" style={{ color: 'var(--dg-text-muted)' }}>
<span>数量:{cluster.count.toLocaleString()}</span>
<span>占比:{cluster.percentage.toFixed(2)}%</span>
</div>
</div>
)}
</div>
))}
</div>
</div>
);
}