远程 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 测试通过
90 lines
4.3 KiB
TypeScript
90 lines
4.3 KiB
TypeScript
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>
|
||
</>
|
||
);
|
||
} |