// UnifiedQueryPanel.tsx — 统一查询面板(接入真实 API)// Logan.Li import { useState, useMemo, useCallback, useRef, useEffect } from 'react'; import { Play, Loader2, Clock, Save, ChevronDown, Bell } from 'lucide-react'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; import { TimeRangePicker } from './TimeRangePicker'; import { LogFieldSidebar } from './LogFieldSidebar'; import { EnhancedLogResults, extractCommonLabels } from './EnhancedLogResults'; import { LogQueryToolbar } from './LogQueryToolbar'; import { StatChartPanel } from './StatChartPanel'; import { LogClusterPanel } from './LogClusterPanel'; import { SaveQueryDialog, SaveAlertDialog } from './SaveQueryDialog'; 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 { useToast } from '@/hooks/use-toast'; // 默认最近5分钟 // Logan.Li const DEFAULT_TIME_RANGE: TimeRange = { start: new Date(Date.now() - 5 * 60000).toISOString().replace('T', ' ').substring(0, 19), end: new Date().toISOString().replace('T', ' ').substring(0, 19), label: '最近5分钟', 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"} |= ""', elasticsearch: 'app:cex-admin-api', }; // 引擎徽标:左侧小圆点 + 语言标签 // Logan.Li const ENGINE_BADGE: Record<'loki' | 'elasticsearch', { label: string; dotColor: string }> = { loki: { label: 'LogQL', dotColor: '#06b6d4' }, elasticsearch: { label: 'Lucene', dotColor: '#22c55e' }, }; // 增强快捷查询模板(14+ 条/引擎) // Logan.Li const ALL_TEMPLATES: Record<'loki' | 'elasticsearch', { label: string; query: string }[]> = { loki: [ { label: 'Nginx 错误', query: '{job="nginx"} |= "error"' }, { label: 'JSON 过滤', query: '{namespace="prod"} | json | level="warn"' }, { label: '超时搜索', query: '{app="api"} |= "timeout"' }, { label: '级别统计', query: 'sum by(level) (count_over_time({job="app"}[5m]))' }, { label: '正则多级别', query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' }, { label: '慢请求>1s', query: '{job="api"} | json | response_time > 1000' }, { label: '错误率统计', query: 'sum by(level) (count_over_time({app="cex-admin-api"} |= "error" [5m]))' }, { label: '追踪ID查询', query: '{app="cex-admin-api"} |= "trace_id" | json' }, { label: '服务聚合', query: 'sum by(service_name) (count_over_time({namespace="astralx"}[5m]))' }, { label: '高延迟筛选', query: '{app="cex-admin-api"} | json | response_time > 500' }, { label: '异常堆栈', query: '{app="cex-admin-api"} |= "Exception"' }, { label: '日志趋势', query: 'sum(count_over_time({app="cex-admin-api"}[1h]))' }, { label: '正则提取', query: '{app="cex-admin-api"} | regexp "(?P\\w+) (?P\\S+)"' }, { label: '格式输出', query: '{app="cex-admin-api"} | json | line_format "{{.level}} {{.message}}"' }, ], elasticsearch: [ { label: 'API 5xx', query: 'status:500 AND path:"/api/*"' }, { label: '错误超时', query: 'level:error AND message:*timeout*' }, { label: '排除调试', query: 'host.name:server-* AND NOT level:debug' }, { label: '慢请求', query: 'response_time:>1000 AND method:POST' }, { label: '严重错误', query: '@timestamp:[now-1h TO now] AND level:critical' }, { label: '服务错误', query: 'service:nginx AND (level:error OR level:fatal)' }, { label: '组合查询', query: 'app:cex-admin-api AND (level:error OR level:warn)' }, { label: '模糊匹配', query: 'message:*timeout* AND app:cex-admin-api' }, { label: '范围查询', query: 'response_time:[1000 TO *] AND method:POST' }, { label: '排除路径', query: 'app:cex-admin-api AND NOT path:/health' }, { label: '时间过滤', query: '@timestamp:[now-1h TO now] AND level:error' }, { label: 'IP段过滤', query: 'client_ip:172.36.* AND app:cex-admin-api' }, { label: '聚合统计', query: 'app:cex-admin-api | STATS count(*) by level' }, { label: '嵌套字段', query: 'labels.orgId:10001 AND app:cex-admin-api' }, ], }; // LogQL / Lucene 语法高亮 → HTML // Logan.Li function highlightSyntax(query: string, engine: 'loki' | 'elasticsearch'): string { if (!query) return '.'; if (engine === 'loki') { return query .replace(/([a-zA-Z_][\w-]*)(?==)/g, '$1') .replace(/("(?:[^"\\]|\\.)*")/g, '$1') .replace(/(\|=|\!=|=~|!~|=)/g, '$1') .replace(/(\|json|\|regexp|\|line_format|\|unwrap|\|pattern|\|label_format)/g, '$1') .replace(/\b(count_over_time|sum|rate|avg|max|min|count|by|over)\b/g, '$1') .replace(/(\{|\})/g, '$1') .replace(/.'; } return query .replace(/\b(AND|OR|NOT)\b/g, '$1') .replace(/([a-zA-Z_@][\w.-]*):/g, '$1:') .replace(/("(?:[^"\\]|\\.)*")/g, '$1') .replace(/(\*|\?|\~|\^|\[|\]|\(|\))/g, '$1') .replace(/.'; } // 语法高亮输入框:底层高亮 + 上层透明输入 // Logan.Li function SyntaxHighlightInput({ value, onChange, engine, placeholder }: { value: string; onChange: (v: string) => void; engine: 'loki' | 'elasticsearch'; placeholder?: string; }) { const taRef = useRef(null); const preRef = useRef(null); const sync = useCallback(() => { if (preRef.current && taRef.current) { preRef.current.scrollLeft = taRef.current.scrollLeft; preRef.current.scrollTop = taRef.current.scrollTop; } }, []); const onKey = (e: React.KeyboardEvent) => { if (e.key === 'Tab') { e.preventDefault(); const t = e.currentTarget, s = t.selectionStart, ed = t.selectionEnd; onChange(value.slice(0, s) + ' ' + value.slice(ed)); requestAnimationFrame(() => { t.selectionStart = t.selectionEnd = s + 2; }); } }; const html = useMemo(() => highlightSyntax(value, engine), [value, engine]); return (