批量转换 163 个文件,3204 处中文替换为 t() 调用: - Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换 - Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换 - Deploy: 流水线/发布/凭证/RBAC 全部转换 - Server/Cloud/Agent/Task/Layout/UI: 全部转换 - 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys) - tsc 编译零错误
112 lines
4.8 KiB
TypeScript
112 lines
4.8 KiB
TypeScript
// LogClusterPanel.tsx — 日志聚类面板 // Logan.Li
|
|
import { useState } from 'react';
|
|
import { ChevronDown, ChevronRight, Copy, Check } from 'lucide-react';
|
|
import type { LogCluster } from './types-v3';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
interface Props {
|
|
clusters: LogCluster[];
|
|
}
|
|
|
|
function PatternHighlight({ pattern }: { pattern: string }) {
|
|
const { t } = useTranslation();
|
|
// 将 * 高亮显示 // 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 { t } = useTranslation();
|
|
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)' }}>{t('monitor.logclusterpanel.s381127')}</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)' }}>{t('monitor.logclusterpanel.se909e4')}</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>
|
|
);
|
|
}
|