From 0c2c0adaafa33ff0a00b3d560e8a7801f335d7b9 Mon Sep 17 00:00:00 2001 From: attacker Date: Sun, 28 Jun 2026 01:32:25 +0800 Subject: [PATCH] =?UTF-8?q?refactor(log-center):=20=E6=B8=85=E7=90=86?= =?UTF-8?q?=E9=87=8D=E5=A4=8D/=E6=AD=BB=E4=BB=A3=E7=A0=81=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=20=E2=80=94=20=E7=A7=BB=E9=99=A4=20DatasourceCard/Dat?= =?UTF-8?q?asourceDialog/DeleteConfirmDialog/EsQueryPanel/LokiQueryPanel/L?= =?UTF-8?q?ogResultsTable/LogLibraryPage/StatusDot/useLogQuery/logLibraryT?= =?UTF-8?q?ypes=2010=20=E4=B8=AA=E5=86=97=E4=BD=99=E7=BB=84=E4=BB=B6=20(17?= =?UTF-8?q?52=20=E8=A1=8C=E5=88=A0=E9=99=A4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monitor/log-center/DatasourceCard.tsx | 245 ----------- .../monitor/log-center/DatasourceDialog.tsx | 394 ------------------ .../log-center/DeleteConfirmDialog.tsx | 113 ----- .../monitor/log-center/EsQueryPanel.tsx | 154 ------- .../monitor/log-center/LogDatasourcePage.tsx | 28 +- .../monitor/log-center/LogLibraryPage.tsx | 269 ------------ .../monitor/log-center/LogResultsTable.tsx | 42 -- .../monitor/log-center/LokiQueryPanel.tsx | 154 ------- .../monitor/log-center/StatusDot.tsx | 27 -- .../monitor/log-center/UnifiedQueryPanel.tsx | 100 ++--- .../monitor/log-center/logLibraryTypes.ts | 90 ---- .../components/monitor/log-center/types-v3.ts | 129 ++---- .../monitor/log-center/useLogQuery.ts | 112 ----- 13 files changed, 105 insertions(+), 1752 deletions(-) delete mode 100644 frontend/src/components/monitor/log-center/DatasourceCard.tsx delete mode 100644 frontend/src/components/monitor/log-center/DatasourceDialog.tsx delete mode 100644 frontend/src/components/monitor/log-center/DeleteConfirmDialog.tsx delete mode 100644 frontend/src/components/monitor/log-center/EsQueryPanel.tsx delete mode 100644 frontend/src/components/monitor/log-center/LogLibraryPage.tsx delete mode 100644 frontend/src/components/monitor/log-center/LogResultsTable.tsx delete mode 100644 frontend/src/components/monitor/log-center/LokiQueryPanel.tsx delete mode 100644 frontend/src/components/monitor/log-center/StatusDot.tsx delete mode 100644 frontend/src/components/monitor/log-center/logLibraryTypes.ts delete mode 100644 frontend/src/components/monitor/log-center/useLogQuery.ts diff --git a/frontend/src/components/monitor/log-center/DatasourceCard.tsx b/frontend/src/components/monitor/log-center/DatasourceCard.tsx deleted file mode 100644 index 834316a..0000000 --- a/frontend/src/components/monitor/log-center/DatasourceCard.tsx +++ /dev/null @@ -1,245 +0,0 @@ -// DatasourceCard.tsx — 数据源卡片组件 // Logan.Li - -import { Check, X, Link, Pencil, Trash2, Loader2 } from 'lucide-react'; -import { ENGINE_CONFIG } from './logLibraryTypes'; -import type { LogDatasource } from './logLibraryTypes'; -import { StatusDot } from './StatusDot'; - -interface DatasourceCardProps { - ds: LogDatasource; - onEdit: (ds: LogDatasource) => void; - onDelete: (ds: LogDatasource) => void; - onTest: (ds: LogDatasource) => void; - testingId: number | null; - testResult: { id: number; success: boolean; message: string } | null; -} - -/** 单个数据源卡片:展示引擎、名称、URL、状态及操作按钮 */ -export function DatasourceCard({ ds, onEdit, onDelete, onTest, testingId, testResult }: DatasourceCardProps) { - const cfg = ENGINE_CONFIG[ds.engine]; - const EngineIcon = cfg.icon; - const isTesting = testingId === ds.id; - const showResult = testResult?.id === ds.id; - - return ( -
{ - (e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; - }} - onMouseLeave={(e) => { - (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; - }} - > - {/* 引擎色条 */} -
- - {/* 头部:图标 + 名称 + 默认标记 */} -
-
- -
-
-
- - {ds.name} - - {ds.isDefault && ( - - 默认 - - )} -
- {cfg.label} -
-
- - {/* URL */} -
- - - {ds.url} - -
- - {/* 描述 */} - {ds.description && ( -
- {ds.description} -
- )} - - {/* 状态行 + 操作按钮 */} -
- - -
- {/* 测试连接 */} - - - {/* 编辑 */} - - - {/* 删除 */} - -
-
- - {/* 测试结果提示 */} - {showResult && ( -
- {testResult.success ? : } - {testResult.message} -
- )} -
- ); -} diff --git a/frontend/src/components/monitor/log-center/DatasourceDialog.tsx b/frontend/src/components/monitor/log-center/DatasourceDialog.tsx deleted file mode 100644 index 30ec62f..0000000 --- a/frontend/src/components/monitor/log-center/DatasourceDialog.tsx +++ /dev/null @@ -1,394 +0,0 @@ -// DatasourceDialog.tsx — 添加/编辑数据源对话框 // Logan.Li - -import { useState, useEffect } from 'react'; -import { X, Check, ChevronDown, Star } from 'lucide-react'; -import { ENGINE_CONFIG } from './logLibraryTypes'; -import type { LogDatasource } from './logLibraryTypes'; - -interface DatasourceDialogProps { - open: boolean; - editing: LogDatasource | null; - onSave: (ds: LogDatasource) => void; - onCancel: () => void; -} - -/** 添加/编辑数据源对话框:引擎选择、名称、URL、描述、认证、默认设置 */ -export function DatasourceDialog({ open, editing, onSave, onCancel }: DatasourceDialogProps) { - const [engine, setEngine] = useState<'loki' | 'elasticsearch'>('loki'); - const [name, setName] = useState(''); - const [url, setUrl] = useState(''); - const [description, setDescription] = useState(''); - const [isDefault, setIsDefault] = useState(false); - const [useAuth, setUseAuth] = useState(false); - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [engineMenuOpen, setEngineMenuOpen] = useState(false); - - // 初始化表单 // Logan.Li - useEffect(() => { - if (editing) { - setEngine(editing.engine); - setName(editing.name); - setUrl(editing.url); - setDescription(editing.description || ''); - setIsDefault(editing.isDefault); - const has = !!editing.auth; - setUseAuth(has); - setUsername(editing.auth?.username || ''); - setPassword(editing.auth?.password || ''); - } else { - setEngine('loki'); - setName(''); - setUrl(''); - setDescription(''); - setIsDefault(false); - setUseAuth(false); - setUsername(''); - setPassword(''); - } - setEngineMenuOpen(false); - }, [editing, open]); - - const handleSave = () => { - if (!name.trim() || !url.trim()) return; - onSave({ - id: editing?.id || Date.now(), - engine, - name: name.trim(), - url: url.trim(), - description: description.trim() || undefined, - status: editing?.status || 'untested', - isDefault, - auth: useAuth && username.trim() ? { username: username.trim(), password: password.trim() } : null, - }); - }; - - const isValid = name.trim() && url.trim() && (!useAuth || username.trim()); - - if (!open) return null; - - const cfg = ENGINE_CONFIG[engine]; - const EngineIcon = cfg.icon; - - return ( -
-
e.stopPropagation()} - > - {/* 标题栏 */} -
-

- {editing ? '编辑数据源' : '添加数据源'} -

- -
- - {/* 表单内容 */} -
- {/* 引擎类型选择 */} -
- -
- - {engineMenuOpen && ( -
- {Object.entries(ENGINE_CONFIG).map(([key, c]) => { - const Icon = c.icon; - return ( - - ); - })} -
- )} -
-
- - {/* 数据源名称 */} -
- - setName(e.target.value)} - placeholder="如:prod-loki" - style={{ width: '100%', padding: '8px 12px', borderRadius: 6, fontSize: 13 }} - /> -
- - {/* URL */} -
- - setUrl(e.target.value)} - placeholder={cfg.placeholder} - style={{ width: '100%', padding: '8px 12px', borderRadius: 6, fontSize: 13 }} - /> -
- - {/* 描述 */} -
- - setDescription(e.target.value)} - placeholder="可选,如:生产环境 Loki" - style={{ width: '100%', padding: '8px 12px', borderRadius: 6, fontSize: 13 }} - /> -
- - {/* Basic Auth 开关 */} - setUseAuth(!useAuth)} - /> - - {/* 认证信息 */} - {useAuth && ( -
-
- - setUsername(e.target.value)} - placeholder="username" - style={{ width: '100%', padding: '8px 12px', borderRadius: 6, fontSize: 13 }} - /> -
-
- - setPassword(e.target.value)} - placeholder="password" - style={{ width: '100%', padding: '8px 12px', borderRadius: 6, fontSize: 13 }} - /> -
-
- )} - - {/* 设为默认 */} - setIsDefault(!isDefault)} - icon={} - /> -
- - {/* 底部按钮 */} -
- - -
-
-
- ); -} - -// ==================== 内联子组件:开关行 ==================== // Logan.Li - -function ToggleRow({ - label, - enabled, - onToggle, - icon, -}: { - label: string; - enabled: boolean; - onToggle: () => void; - icon?: React.ReactNode; -}) { - return ( -
- - {icon} - {label} - - -
- ); -} diff --git a/frontend/src/components/monitor/log-center/DeleteConfirmDialog.tsx b/frontend/src/components/monitor/log-center/DeleteConfirmDialog.tsx deleted file mode 100644 index 8ed5378..0000000 --- a/frontend/src/components/monitor/log-center/DeleteConfirmDialog.tsx +++ /dev/null @@ -1,113 +0,0 @@ -// DeleteConfirmDialog.tsx — 删除确认对话框 // Logan.Li - -import { AlertTriangle } from 'lucide-react'; -import type { LogDatasource } from './logLibraryTypes'; - -interface DeleteConfirmDialogProps { - open: boolean; - datasource: LogDatasource | null; - hasAlerts: boolean; - onConfirm: () => void; - onCancel: () => void; -} - -/** 删除数据源二次确认对话框 */ -export function DeleteConfirmDialog({ - open, - datasource, - hasAlerts, - onConfirm, - onCancel, -}: DeleteConfirmDialogProps) { - if (!open || !datasource) return null; - - return ( -
-
e.stopPropagation()} - > - {/* 标题 */} -
- -

- 确认删除数据源 -

-
- - {/* 内容 */} -

- 确定要删除数据源 {datasource.name} 吗? - 此操作不可恢复。 -

- - {hasAlerts && ( -
- - 该数据源有关联的告警规则,请先删除告警后再删除数据源。 -
- )} - - {/* 按钮 */} -
- - -
-
-
- ); -} diff --git a/frontend/src/components/monitor/log-center/EsQueryPanel.tsx b/frontend/src/components/monitor/log-center/EsQueryPanel.tsx deleted file mode 100644 index 097568c..0000000 --- a/frontend/src/components/monitor/log-center/EsQueryPanel.tsx +++ /dev/null @@ -1,154 +0,0 @@ -// EsQueryPanel — Elasticsearch 查询 // Logan.Li -import { useState, useEffect, useCallback } from 'react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Play, Plus, RefreshCw, Loader2 } from 'lucide-react'; -import { useToast } from '@/hooks/use-toast'; -import { logCenterApi, type LogDataSource, type LogQueryEntry } from '@/api/monitor/log-center'; -import { LogDatasourceDialog } from './LogDatasourceDialog'; -import { LogResultsTable } from './LogResultsTable'; -import { useLogQueryContext } from './LogQueryContext'; - -const TIME_PRESETS = [ - { label: '15 分钟', value: '15m' }, - { label: '1 小时', value: '1h' }, - { label: '6 小时', value: '6h' }, - { label: '24 小时', value: '24h' }, -]; - -function presetToStart(preset: string): string { - const m = preset.match(/^(\d+)(m|h)$/); - if (!m) return new Date(Date.now() - 3600000).toISOString(); - const n = parseInt(m[1], 10); - const ms = m[2] === 'h' ? n * 3600000 : n * 60000; - return new Date(Date.now() - ms).toISOString(); -} - -export function EsQueryPanel() { - const { toast } = useToast(); - const { setContext } = useLogQueryContext(); - const [sources, setSources] = useState([]); - const [dsId, setDsId] = useState(''); - const [index, setIndex] = useState('logstash-*'); - const [query, setQuery] = useState('level:error AND message:*timeout*'); - const [timePreset, setTimePreset] = useState('1h'); - const [limit, setLimit] = useState(200); - const [entries, setEntries] = useState([]); - const [loading, setLoading] = useState(false); - const [dsDialog, setDsDialog] = useState(false); - - const loadSources = useCallback(async () => { - const list = await logCenterApi.listDatasources('elasticsearch').catch(() => []); - const arr = Array.isArray(list) ? list : []; - setSources(arr); - if (!dsId && arr.length) { - const def = arr.find(s => s.is_default) || arr[0]; - setDsId(String(def.id)); - if (def.default_index) setIndex(def.default_index); - } - }, [dsId]); - - useEffect(() => { loadSources(); }, [loadSources]); - - const syncContext = useCallback((q: string, id: string, idx: string) => { - const ds = sources.find(s => String(s.id) === id); - setContext({ - engine: 'elasticsearch', - datasourceId: ds ? ds.id : undefined, - datasourceName: ds?.name, - query: q, - index: idx, - timeRange: timePreset, - }); - }, [sources, timePreset, setContext]); - - useEffect(() => { - syncContext(query, dsId, index); - }, [query, dsId, index, syncContext]); - - const runQuery = async () => { - if (!dsId) return; - setLoading(true); - try { - const res = await logCenterApi.queryElasticsearch({ - datasource_id: Number(dsId), - index: index.trim() || undefined, - query: query.trim() || '*', - size: limit, - start: presetToStart(timePreset), - end: new Date().toISOString(), - }); - setEntries(res?.entries || []); - toast({ title: '查询完成', description: `共 ${res?.total ?? 0} 条` }); - } catch (e: any) { - toast({ title: '查询失败', description: e?.message || String(e), variant: 'destructive' }); - setEntries([]); - } finally { - setLoading(false); - } - }; - - return ( -
-
-
- -
- - - -
-
-
- - setIndex(e.target.value)} className="h-9 mt-1 font-mono text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" /> -
-
- - -
- -
- -
- - setQuery(e.target.value)} - className="mt-1 font-mono text-xs h-10 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" - placeholder='status:500 AND path:"/api/*"' - /> -

支持 Lucene 语法;留空则匹配全部 · 单次最多 500 条,不落库

-
- - - - -
- ); -} \ No newline at end of file diff --git a/frontend/src/components/monitor/log-center/LogDatasourcePage.tsx b/frontend/src/components/monitor/log-center/LogDatasourcePage.tsx index baaa0dc..5e7bf1c 100644 --- a/frontend/src/components/monitor/log-center/LogDatasourcePage.tsx +++ b/frontend/src/components/monitor/log-center/LogDatasourcePage.tsx @@ -12,16 +12,10 @@ import { logCenterApi, type LogDataSource } from '@/api/monitor/log-center'; import { LogDatasourceDialog } from './LogDatasourceDialog'; import type { LogEngine } from './log-center-types'; -// Demo 兜底数据源(API 无数据时展示) // Logan.Li -const DEMO_FALLBACK: LogDataSource[] = [ - { id: -1, name: 'demo-loki', type: 'loki', url: '' }, - { id: -2, name: 'demo-es', type: 'elasticsearch', url: '' }, -]; - export function LogDatasourcePage() { const { toast } = useToast(); const confirm = useConfirm(); - const [sources, setSources] = useState(DEMO_FALLBACK); + const [sources, setSources] = useState([]); const [loading, setLoading] = useState(true); const [dialog, setDialog] = useState<{ open: boolean; engine: LogEngine; editing?: LogDataSource | null }>({ open: false, engine: 'loki', editing: null, @@ -32,10 +26,9 @@ export function LogDatasourcePage() { try { const res = await logCenterApi.listDatasources(); const apiList: LogDataSource[] = Array.isArray(res) ? res : []; - // 真实数据源在前 + demo 兜底在后 // Logan.Li - setSources([...apiList, ...DEMO_FALLBACK]); + setSources(apiList); } catch { - setSources(DEMO_FALLBACK); + setSources([]); } finally { setLoading(false); } @@ -116,31 +109,28 @@ export function LogDatasourcePage() { }, { key: 'actions', header: '操作', align: 'right', hideable: false, - render: (ds) => { - const isDemo = ds.id < 0 || !ds.url; - return ( + render: (ds) => (
{!ds.is_default && ( )}
- ); - }, + ), }, ]; diff --git a/frontend/src/components/monitor/log-center/LogLibraryPage.tsx b/frontend/src/components/monitor/log-center/LogLibraryPage.tsx deleted file mode 100644 index de87a5e..0000000 --- a/frontend/src/components/monitor/log-center/LogLibraryPage.tsx +++ /dev/null @@ -1,269 +0,0 @@ -// LogLibraryPage.tsx — 日志库(数据源管理)主页面 // Logan.Li -// 组装卡片列表、编辑对话框、删除确认对话框,管理全局状态 - -import { useState, useEffect, useCallback } from 'react'; -import { Database, Flame, Plus, Check, Server } from 'lucide-react'; -import { loadDatasources, saveDatasources, ENGINE_CONFIG } from './logLibraryTypes'; -import type { LogDatasource } from './logLibraryTypes'; -import { DatasourceCard } from './DatasourceCard'; -import { DatasourceDialog } from './DatasourceDialog'; -import { DeleteConfirmDialog } from './DeleteConfirmDialog'; - -export function LogLibraryPage() { - const [datasources, setDatasources] = useState(loadDatasources); - const [dialogOpen, setDialogOpen] = useState(false); - const [editing, setEditing] = useState(null); - const [deleteOpen, setDeleteOpen] = useState(false); - const [deleting, setDeleting] = useState(null); - const [testingId, setTestingId] = useState(null); - const [testResult, setTestResult] = useState<{ id: number; success: boolean; message: string } | null>(null); - - // 数据变更时持久化到 localStorage // Logan.Li - useEffect(() => { - saveDatasources(datasources); - }, [datasources]); - - // 统计数据 // Logan.Li - const lokiCount = datasources.filter((d) => d.engine === 'loki').length; - const esCount = datasources.filter((d) => d.engine === 'elasticsearch').length; - const connectedCount = datasources.filter((d) => d.status === 'connected').length; - - // 打开添加对话框 // Logan.Li - const openCreate = useCallback(() => { - setEditing(null); - setDialogOpen(true); - }, []); - - // 打开编辑对话框 // Logan.Li - const openEdit = useCallback((ds: LogDatasource) => { - setEditing(ds); - setDialogOpen(true); - }, []); - - // 保存数据源(新增或更新) // Logan.Li - const handleSave = useCallback((ds: LogDatasource) => { - setDatasources((prev) => { - // 若设为默认,取消同引擎其他数据源的默认标记 // Logan.Li - let next = prev.map((item) => - ds.isDefault && item.engine === ds.engine && item.id !== ds.id - ? { ...item, isDefault: false } - : item - ); - const exists = next.find((item) => item.id === ds.id); - if (exists) { - next = next.map((item) => (item.id === ds.id ? ds : item)); - } else { - next = [...next, ds]; - } - return next; - }); - setDialogOpen(false); - }, []); - - // 打开删除确认 // Logan.Li - const openDelete = useCallback((ds: LogDatasource) => { - setDeleting(ds); - setDeleteOpen(true); - }, []); - - // 确认删除 // Logan.Li - const confirmDelete = useCallback(() => { - if (!deleting) return; - setDatasources((prev) => prev.filter((d) => d.id !== deleting.id)); - setDeleteOpen(false); - setDeleting(null); - }, [deleting]); - - // 测试连接 // Logan.Li - const handleTest = useCallback((ds: LogDatasource) => { - setTestingId(ds.id); - setTestResult(null); - - // 模拟异步测试 // Logan.Li - setTimeout(() => { - const success = ds.status !== 'error'; - const streamCount = Math.floor(Math.random() * 50) + 10; - setTestResult({ - id: ds.id, - success, - message: success - ? `连接成功,${streamCount} 个日志流` - : '连接失败:无法访问目标地址 (timeout)', - }); - // 同步更新数据源状态 // Logan.Li - setDatasources((prev) => - prev.map((d) => - d.id === ds.id ? { ...d, status: success ? 'connected' : 'error' } : d - ) - ); - setTestingId(null); - }, 1200); - }, []); - - // 检查数据源是否有关联告警 // Logan.Li - const hasRelatedAlerts = useCallback((ds: LogDatasource): boolean => { - try { - const alertsRaw = localStorage.getItem('log-center:alerts'); - if (alertsRaw) { - const alerts = JSON.parse(alertsRaw); - return alerts.some((a: { datasourceId?: number; datasourceName?: string }) => - a.datasourceId === ds.id || a.datasourceName === ds.name - ); - } - } catch { /* ignore */ } - return false; - }, []); - - return ( -
- {/* 页面标题 + 统计 */} -
-
-

- - 日志库 -

-

- 管理 Loki 和 Elasticsearch 数据源配置 -

-
- - {/* 统计 + 添加按钮 */} -
- } label="Loki" value={lokiCount} valueColor={ENGINE_CONFIG.loki.color} /> - } label="ES" value={esCount} valueColor={ENGINE_CONFIG.elasticsearch.color} /> - } label="正常" value={connectedCount} valueColor="var(--dg-green)" /> - - -
-
- - {/* 分隔线 */} -
- - {/* 数据源卡片网格 */} - {datasources.length === 0 ? ( - - ) : ( -
- {datasources.map((ds) => ( - - ))} -
- )} - - {/* 子对话框 */} - setDialogOpen(false)} /> - { setDeleteOpen(false); setDeleting(null); }} - /> -
- ); -} - -// ==================== 内联子组件:统计徽章 ==================== // Logan.Li - -function StatBadge({ - icon, - label, - value, - valueColor, -}: { - icon: React.ReactNode; - label: string; - value: number; - valueColor: string; -}) { - return ( -
- {icon} - {label} - {value} -
- ); -} - -// ==================== 内联子组件:空状态 ==================== // Logan.Li - -function EmptyState() { - return ( -
- -

暂无数据源

-

点击右上角「添加数据源」按钮创建

-
- ); -} diff --git a/frontend/src/components/monitor/log-center/LogResultsTable.tsx b/frontend/src/components/monitor/log-center/LogResultsTable.tsx deleted file mode 100644 index 9edf382..0000000 --- a/frontend/src/components/monitor/log-center/LogResultsTable.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// LogResultsTable — 日志查询结果表格 // Logan.Li -import { Badge } from '@/components/ui/badge'; -import type { LogQueryEntry } from '@/api/monitor/log-center'; - -const levelColor: Record = { - error: 'bg-red-500/15 text-red-400 border-red-500/30', - warn: 'bg-amber-500/15 text-amber-400 border-amber-500/30', - warning: 'bg-amber-500/15 text-amber-400 border-amber-500/30', - info: 'bg-blue-500/15 text-blue-400 border-blue-500/30', - debug: 'bg-slate-500/15 text-slate-400 border-slate-500/30', -}; - -export function LogResultsTable({ entries, loading }: { entries: LogQueryEntry[]; loading?: boolean }) { - if (loading) { - return
查询中...
; - } - if (entries.length === 0) { - return
暂无日志,调整查询条件后重试
; - } - return ( -
-
- 时间 - 级别 - 内容 -
-
- {entries.map((e, i) => ( -
- {e.timestamp} - - {e.level ? ( - {e.level} - ) : '—'} - -
{e.message}
-
- ))} -
-
- ); -} \ No newline at end of file diff --git a/frontend/src/components/monitor/log-center/LokiQueryPanel.tsx b/frontend/src/components/monitor/log-center/LokiQueryPanel.tsx deleted file mode 100644 index 3a9aa93..0000000 --- a/frontend/src/components/monitor/log-center/LokiQueryPanel.tsx +++ /dev/null @@ -1,154 +0,0 @@ -// LokiQueryPanel — LogQL 查询 // Logan.Li -import { useState, useEffect, useCallback } from 'react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Play, Plus, RefreshCw, Loader2 } from 'lucide-react'; -import { useToast } from '@/hooks/use-toast'; -import { logCenterApi, type LogDataSource, type LogQueryEntry } from '@/api/monitor/log-center'; -import { LogDatasourceDialog } from './LogDatasourceDialog'; -import { LogResultsTable } from './LogResultsTable'; -import { useLogQueryContext } from './LogQueryContext'; - -const TIME_PRESETS = [ - { label: '15 分钟', value: '15m' }, - { label: '1 小时', value: '1h' }, - { label: '6 小时', value: '6h' }, - { label: '24 小时', value: '24h' }, -]; - -function presetToStart(preset: string): string { - const m = preset.match(/^(\d+)(m|h)$/); - if (!m) return new Date(Date.now() - 3600000).toISOString(); - const n = parseInt(m[1], 10); - const ms = m[2] === 'h' ? n * 3600000 : n * 60000; - return new Date(Date.now() - ms).toISOString(); -} - -export function LokiQueryPanel() { - const { toast } = useToast(); - const { setContext } = useLogQueryContext(); - const [sources, setSources] = useState([]); - const [dsId, setDsId] = useState(''); - const [query, setQuery] = useState('{namespace="default"} |= "error"'); - const [timePreset, setTimePreset] = useState('1h'); - const [limit, setLimit] = useState(200); - const [entries, setEntries] = useState([]); - const [loading, setLoading] = useState(false); - const [dsDialog, setDsDialog] = useState(false); - - const loadSources = useCallback(async () => { - const list = await logCenterApi.listDatasources('loki').catch(() => []); - const arr = Array.isArray(list) ? list : []; - setSources(arr); - if (!dsId && arr.length) { - const def = arr.find(s => s.is_default) || arr[0]; - setDsId(String(def.id)); - } - }, [dsId]); - - useEffect(() => { loadSources(); }, [loadSources]); - - const syncContext = useCallback((q: string, id: string) => { - const ds = sources.find(s => String(s.id) === id); - setContext({ - engine: 'loki', - datasourceId: ds ? ds.id : undefined, - datasourceName: ds?.name, - query: q, - timeRange: timePreset, - }); - }, [sources, timePreset, setContext]); - - useEffect(() => { - syncContext(query, dsId); - }, [query, dsId, syncContext]); - - const runQuery = async () => { - if (!dsId || !query.trim()) return; - setLoading(true); - try { - const res = await logCenterApi.queryLoki({ - datasource_id: Number(dsId), - query: query.trim(), - limit, - start: presetToStart(timePreset), - end: new Date().toISOString(), - }); - setEntries(res?.entries || []); - toast({ title: '查询完成', description: `共 ${res?.total ?? 0} 条` }); - } catch (e: any) { - toast({ title: '查询失败', description: e?.message || String(e), variant: 'destructive' }); - setEntries([]); - } finally { - setLoading(false); - } - }; - - return ( -
-
-
- -
- - - -
-
-
- - -
-
- - setLimit(Number(e.target.value) || 200)} className="h-9 mt-1 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" /> -
- -
- -
- -