Files
aiops/frontend/src/components/monitor/log-center/SavedQueriesPanel.tsx
T
attacker da9fd1e987 feat(quality): U12 工程质量回归修复 — ESLint errors(8→0) + React Hooks 关键规则(82→0)
远程 i18n 全量改造 + 堡垒机合并后引入两类质量回归,本次修复:

1. ESLint errors 8→0:
   - GrafanaDashboard/PrometheusDashboard: 未使用的 const { t } 改为 useTranslation()
   - ServerTableRow: 移除未使用 cn import
   - replay-utils: no-control-regex 标注 disable (终端回放有意检测控制字符)

2. React Hooks 6 大关键规则 82→0 (恢复 U7 治理水平):
   - exhaustive-deps 52→0 (mount-only effects 标注 disable)
   - set-state-in-effect 18→0 (effect 初始化模式)
   - purity 5→0 (AlertCenter 提取 nowMs / EnhancedLogResults 骨架屏确定性公式)
   - incompatible-library 3→0 (@tanstack/react-virtual 兼容性)
   - immutability 2→0 (AlertCenter TDZ)
   - preserve-manual-memoization 2→0 (StatChartPanel useMemo)

验证: tsc -b=0, npm build✓(18.04s), go build=0, ESLint errors=0, 17/17 测试通过
2026-07-06 00:55:01 +08:00

90 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { X, Trash2, Play, Bookmark, Clock } from 'lucide-react';
import type { SavedQuery } from './types-v3';
import { savedQueryStorage } from './types-v3';
import { ENGINE_META } from './types-v3';
import i18n from '@/i18n';
interface Props {
visible: boolean;
onClose: () => void;
onSelect: (item: SavedQuery) => void;
}
// 已保存查询面板(localStorage)// Logan.Li
export function SavedQueriesPanel({ visible, onClose, onSelect }: Props) {
const [items, setItems] = useState<SavedQuery[]>([]);
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
if (visible) setItems(savedQueryStorage.getAll());
}, [visible]);
if (!visible) return null;
return (
<>
<div className="fixed inset-0 z-40" style={{ background: 'rgba(0,0,0,0.3)' }} onClick={onClose} />
<div
className="fixed right-0 top-0 h-full z-50 border-l flex flex-col"
style={{ width: '380px', background: 'var(--dg-bg-secondary)', borderColor: 'var(--dg-border)' }}
>
<div className="flex items-center justify-between px-4 py-3 border-b" style={{ borderColor: 'var(--dg-border)' }}>
<div className="flex items-center gap-2">
<Bookmark className="h-4 w-4" style={{ color: 'var(--dg-blue)' }} />
<span className="text-sm font-medium" style={{ color: 'var(--dg-text-primary)' }}> {i18n.t('monitor.logPanel.savedQueries')} </span>
<span className="text-[10px] px-1.5 py-0.5 rounded" style={{ background: 'var(--dg-bg-tertiary)', color: 'var(--dg-text-muted)' }}>
{items.length}
</span>
</div>
<button onClick={onClose} className="h-7 w-7 flex items-center justify-center" style={{ color: 'var(--dg-text-muted)' }}>
<X className="h-4 w-4" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-3 space-y-2">
{items.length === 0 ? (
<p className="text-xs text-center py-8" style={{ color: 'var(--dg-text-muted)' }}> {i18n.t('monitor.savedqueriespan.s9c8dc62b')} </p>
) : items.map((item) => (
<div
key={item.id}
className="p-3 rounded-lg border group"
style={{ background: 'var(--dg-bg-tertiary)', borderColor: 'var(--dg-border)' }}
>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1">
<p className="text-sm font-medium truncate" style={{ color: 'var(--dg-text-primary)' }}>{item.name}</p>
<p className="text-[10px] mt-0.5" style={{ color: ENGINE_META[item.engine]?.color || 'var(--dg-text-muted)' }}>
{ENGINE_META[item.engine]?.name} · {item.timeRangeLabel}
</p>
<p className="text-[11px] font-mono mt-1 truncate" style={{ color: 'var(--dg-text-secondary)' }}>{item.query}</p>
<p className="text-[10px] mt-1 flex items-center gap-1" style={{ color: 'var(--dg-text-muted)' }}>
<Clock className="h-3 w-3" />
{new Date(item.createdAt).toLocaleString()}
</p>
</div>
<div className="flex flex-col gap-1 opacity-80 group-hover:opacity-100">
<button
onClick={() => { onSelect(item); onClose(); }}
className="h-7 w-7 flex items-center justify-center rounded border"
style={{ borderColor: 'var(--dg-border)', color: '#22c55e' }}
title={i18n.t('kubernetes.yaml.apply')}
>
<Play className="h-3.5 w-3.5" />
</button>
<button
onClick={() => { savedQueryStorage.remove(item.id); setItems(savedQueryStorage.getAll()); }}
className="h-7 w-7 flex items-center justify-center rounded border"
style={{ borderColor: 'var(--dg-border)', color: 'var(--dg-text-muted)' }}
title={i18n.t('kubernetes.storage.delete2')}
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
</div>
</div>
))}
</div>
</div>
</>
);
}