feat(log-center): 日志中心 v4 全量组件 — 统一查询面板/时间选择器/统计图表/聚类/数据源管理
This commit is contained in:
1 parent
880cf050fe
commit
bdf3c2daf4
30 files changed
+4276
-136
No files matched your search
@@ -0,0 +1,108 @@
|
||||
// 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 text-green-400" /> : <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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user