refactor(log-center): 清理重复/死代码组件 — 移除 DatasourceCard/DatasourceDialog/DeleteConfirmDialog/EsQueryPanel/LokiQueryPanel/LogResultsTable/LogLibraryPage/StatusDot/useLogQuery/logLibraryTypes 10 个冗余组件 (1752 行删除)

This commit is contained in:
attacker committed 2026-06-28 01:32:25 +08:00
1 parent 0c1964aa85
commit 0c2c0adaaf
13 files changed
+105 -1752

No files matched your search

@@ -13,7 +13,7 @@ import { QueryHistoryPanel } from './QueryHistoryPanel';
import { LogAlertFromQueryDialog } from './LogAlertFromQueryDialog';
import { logCenterApi, type LogDataSource } from '@/api/monitor/log-center';
import type { LogEngine, TimeRange, ResultTab, ViewMode, LogEntry, StatTable, LogCluster, QueryHistoryItem } from './types-v3';
import { ENGINE_META, generateMockEntries, generateMockStatTable, generateMockClusters, extractFields, generateHistogramData, historyStorage } from './types-v3';
import { ENGINE_META, buildStatTableFromEntries, buildClustersFromEntries, extractFields, generateHistogramData, historyStorage } from './types-v3';
import { useToast } from '@/hooks/use-toast';
// 默认最近5分钟 // Logan.Li
@@ -24,12 +24,6 @@ const DEFAULT_TIME_RANGE: TimeRange = {
type: 'relative',
};
// Demo 数据源(后端无数据源时的兜底)// Logan.Li
const DEMO_SOURCES: Record<'loki' | 'elasticsearch', LogDataSource[]> = {
loki: [{ id: -1, name: 'demo-loki', type: 'loki', url: '' }],
elasticsearch: [{ id: -2, name: 'demo-es', type: 'elasticsearch', url: '' }],
};
// 引擎默认查询语句 // Logan.Li
const DEFAULT_QUERIES: Record<'loki' | 'elasticsearch', string> = {
loki: '{app="cex-admin-api"} |= ""',
@@ -81,22 +75,34 @@ const ALL_TEMPLATES: Record<'loki' | 'elasticsearch', { label: string; query: st
// LogQL / Lucene 语法高亮 → HTML // Logan.Li
function highlightSyntax(query: string, engine: 'loki' | 'elasticsearch'): string {
if (!query) return '<span style="color:transparent">.</span>';
// 先转义 HTML 特殊字符,避免查询语句中的 < > & 破坏标签结构 // Logan.Li
const esc = query.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
const span = (color: string, text: string) => `<span style="color:${color}">${text}</span>`;
if (engine === 'loki') {
return query
.replace(/([a-zA-Z_][\w-]*)(?==)/g, '<s c="#f59e0b">$1</s>')
.replace(/("(?:[^"\\]|\\.)*")/g, '<s c="#22c55e">$1</s>')
.replace(/(\|=|\!=|=~|!~|=)/g, '<s c="#f43f5e">$1</s>')
.replace(/(\|json|\|regexp|\|line_format|\|unwrap|\|pattern|\|label_format)/g, '<s c="#8b5cf6">$1</s>')
.replace(/\b(count_over_time|sum|rate|avg|max|min|count|by|over)\b/g, '<s c="#06b6d4">$1</s>')
.replace(/(\{|\})/g, '<s c="#f59e0b">$1</s>')
.replace(/<s c=/g, '<span style="color=') + '<span style="color:transparent">.</span>';
// 单正则 + 回调:一次匹配,避免链式 replace 对已生成颜色值的二次匹配 // Logan.Li
return esc.replace(
/("(?:[^"\\]|\\.)*")|([a-zA-Z_][\w-]*)(?==)|(\|json|\|regexp|\|line_format|\|unwrap|\|pattern|\|label_format)|\b(count_over_time|sum|rate|avg|max|min|count|by|over)\b|([{}])|(\|=|\!=|=~|!~|=)/g,
(m, str, ident, pipe, fn, brace, op) => {
if (str) return span('#22c55e', str);
if (ident) return span('#f59e0b', ident);
if (pipe) return span('#8b5cf6', pipe);
if (fn) return span('#06b6d4', fn);
if (brace) return span('#f59e0b', brace);
if (op) return span('#f43f5e', op);
return m;
},
) + '<span style="color:transparent">.</span>';
}
return query
.replace(/\b(AND|OR|NOT)\b/g, '<s c="#f43f5e">$1</s>')
.replace(/([a-zA-Z_@][\w.-]*):/g, '<s c="#f59e0b">$1</s>:')
.replace(/("(?:[^"\\]|\\.)*")/g, '<s c="#22c55e">$1</s>')
.replace(/(\*|\?|\~|\^|\[|\]|\(|\))/g, '<s c="#8b5cf6">$1</s>')
.replace(/<s c=/g, '<span style="color=') + '<span style="color:transparent">.</span>';
return esc.replace(
/("(?:[^"\\]|\\.)*")|\b(AND|OR|NOT)\b|([a-zA-Z_@][\w.-]*)(?=:)|([*?~^[\]()])/g,
(m, str, kw, field, punct) => {
if (str) return span('#22c55e', str);
if (kw) return span('#f43f5e', kw);
if (field) return span('#f59e0b', field);
if (punct) return span('#8b5cf6', punct);
return m;
},
) + '<span style="color:transparent">.</span>';
}
// 语法高亮输入框:底层高亮 + 上层透明输入 // Logan.Li
@@ -132,15 +138,13 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
const meta = ENGINE_META[effEngine];
const badge = ENGINE_BADGE[effEngine];
// 从后端加载真实数据源,同时保留 demo 兜底 // Logan.Li
const [sources, setSources] = useState<LogDataSource[]>(DEMO_SOURCES[effEngine]);
// 从后端加载真实数据源(无 demo 兜底,无源时引导用户前往日志库配置)// Logan.Li
const [sources, setSources] = useState<LogDataSource[]>([]);
useEffect(() => {
logCenterApi.listDatasources(effEngine).then((res: any) => {
const list: LogDataSource[] = Array.isArray(res) ? res : (res?.list || []);
// 合并真实数据源 + demo 兜底(真实源在前) // Logan.Li
if (list.length > 0) setSources([...list, ...DEMO_SOURCES[effEngine]]);
else setSources(DEMO_SOURCES[effEngine]);
}).catch(() => setSources(DEMO_SOURCES[effEngine]));
setSources(list);
}).catch(() => setSources([]));
}, [effEngine]);
const [query, setQuery] = useState(DEFAULT_QUERIES[effEngine]);
@@ -168,13 +172,15 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
const [saveMenuOpen, setSaveMenuOpen] = useState(false);
const [hasQueried, setHasQueried] = useState(false);
// 数据源切换后更新 dsId // Logan.Li
// 数据源加载后选中默认或首个(无源时保持空)// Logan.Li
useEffect(() => {
if (sources.length > 0) setDsId(String(sources[0].id));
if (sources.length > 0) {
const def = sources.find(s => s.is_default) || sources[0];
setDsId(String(def.id));
}
}, [sources]);
const currentSource = sources.find(s => String(s.id) === dsId);
const isDemo = currentSource && (currentSource.id < 0 || !currentSource.url);
const fields = useMemo(() => extractFields(entries), [entries]);
const commonLabels = useMemo(() => extractCommonLabels(entries), [entries]);
@@ -182,26 +188,13 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
const totalPages = Math.max(1, Math.ceil(sortedEntries.length / perPage));
const pagedEntries = useMemo(() => { const s = (page - 1) * perPage; return sortedEntries.slice(s, s + perPage); }, [sortedEntries, page, perPage]);
// 执行查询 — 接入真实 API(demo 源则使用 mock)// Logan.Li
// 执行查询 — 接入真实 API(无 mock)// Logan.Li
const handleRun = useCallback(async () => {
if (!currentSource) return;
const dsIdNum = currentSource.id;
setLoading(true);
const t0 = performance.now();
// Demo 源:模拟数据
if (isDemo) {
setTimeout(() => {
const mock = generateMockEntries(effEngine as LogEngine, 120);
setEntries(mock); setHistogramData(generateHistogramData(mock)); setTotalCount(mock.length * 14);
setQueryTime(Math.round(performance.now() - t0)); setStatTable(generateMockStatTable(effEngine as LogEngine));
setClusters(generateMockClusters()); setPage(1); setLoading(false); setHasQueried(true);
historyStorage.add({ engine: effEngine as LogEngine, datasourceName: currentSource.name, query, timeRange: timeRange.label, resultCount: mock.length });
}, 300);
return;
}
// 真实 API 查询
try {
const startISO = (timeRange.start + (timeRange.start.includes('T') ? '' : '.000Z')).replace(' ', 'T');
const endISO = (timeRange.end + (timeRange.end.includes('T') ? '' : '.000Z')).replace(' ', 'T');
@@ -241,8 +234,9 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
setHistogramData(generateHistogramData(mapped));
setTotalCount(data?.total || mapped.length);
setQueryTime(Math.round(performance.now() - t0));
setStatTable(generateMockStatTable(effEngine as LogEngine));
setClusters(generateMockClusters());
// 统计图表 / 聚类基于真实返回条目聚合(无 mock)// Logan.Li
setStatTable(buildStatTableFromEntries(mapped));
setClusters(buildClustersFromEntries(mapped));
setPage(1);
setHasQueried(true);
historyStorage.add({ engine: effEngine as LogEngine, datasourceName: currentSource.name, query, timeRange: timeRange.label, resultCount: mapped.length });
@@ -252,7 +246,7 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
} finally {
setLoading(false);
}
}, [effEngine, query, currentSource, timeRange, isDemo, toast]);
}, [effEngine, query, currentSource, timeRange, toast]);
const toggleField = useCallback((n: string) => setSelectedFields(p => p.includes(n) ? p.filter(f => f !== n) : [...p, n]), []);
const handleSaveMenu = (a: 'query' | 'alert') => { setSaveMenuOpen(false); a === 'query' ? setSaveQueryOpen(true) : setSaveAlertOpen(true); };
@@ -280,10 +274,11 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
<TimeRangePicker value={timeRange} onChange={setTimeRange} />
<select value={dsId} onChange={e => setDsId(e.target.value)} className="h-10 px-2 text-xs rounded flex-shrink-0" style={{ background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)' }}>
{sources.map(s => <option key={s.id} value={s.id}>{s.name}{(s.id < 0 || !s.url) ? ' (模拟)' : ''}</option>)}
{sources.length === 0 && <option value="">无可用{meta.name}数据源</option>}
{sources.map(s => <option key={s.id} value={s.id}>{s.name}{s.is_default ? ' (默认)' : ''}</option>)}
</select>
<button onClick={handleRun} disabled={loading} className="h-10 px-4 rounded text-xs font-medium text-white flex items-center gap-1.5 flex-shrink-0 transition-opacity hover:opacity-90" style={{ background: '#22c55e' }}>
<button onClick={handleRun} disabled={loading || !currentSource} className="h-10 px-4 rounded text-xs font-medium text-white flex items-center gap-1.5 flex-shrink-0 transition-opacity hover:opacity-90" style={{ background: '#22c55e' }}>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Play className="h-4 w-4" />} 查询
</button>
@@ -305,6 +300,13 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
</button>
</div>
{/* 无数据源时引导用户前往日志库配置 // Logan.Li */}
{sources.length === 0 && (
<div className="p-3 rounded border text-xs" style={{ background: 'rgba(245,158,11,0.1)', borderColor: 'rgba(245,158,11,0.3)', color: '#f59e0b' }}>
暂无{meta.name}数据源,请先前往「日志库」添加数据源。
</div>
)}
{/* 快捷查询助手 */}
<QuickQueryBar engine={effEngine} templates={ALL_TEMPLATES[effEngine]} onSelect={setQuery} />