HistoryManager speichert ausgefuehrte Queries mit Metriken (Laufzeit, Zeilenanzahl), Deduplizierung, max 200 Eintraege, Favoriten-Schutz vor Loeschung, Suche. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
126 lines
3.4 KiB
TypeScript
126 lines
3.4 KiB
TypeScript
// ---------------------------------------------------------------------------
|
|
// Query History — automatisches Tracking aller ausgefuehrten Queries
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface HistoryEntry {
|
|
id: string;
|
|
sql: string;
|
|
executedAt: string;
|
|
executionTimeMs?: number;
|
|
rowCount?: number;
|
|
isFavorite: boolean;
|
|
name?: string;
|
|
}
|
|
|
|
const STORAGE_KEY = 'logfire-query-history';
|
|
const MAX_ENTRIES = 200;
|
|
|
|
export class HistoryManager {
|
|
private entries = new Map<string, HistoryEntry>();
|
|
private listeners = new Set<() => void>();
|
|
|
|
constructor() {
|
|
this.load();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Persistence
|
|
// ---------------------------------------------------------------------------
|
|
|
|
private load(): void {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (!raw) return;
|
|
try {
|
|
const arr: HistoryEntry[] = JSON.parse(raw);
|
|
for (const e of arr) this.entries.set(e.id, e);
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
private save(): void {
|
|
localStorage.setItem(
|
|
STORAGE_KEY,
|
|
JSON.stringify(Array.from(this.entries.values())),
|
|
);
|
|
for (const fn of this.listeners) fn();
|
|
}
|
|
|
|
onChange(fn: () => void): () => void {
|
|
this.listeners.add(fn);
|
|
return () => this.listeners.delete(fn);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CRUD
|
|
// ---------------------------------------------------------------------------
|
|
|
|
addEntry(sql: string, executionTimeMs?: number, rowCount?: number): HistoryEntry {
|
|
// Deduplizieren: gleiche SQL aktualisiert vorhandenen Eintrag
|
|
for (const entry of this.entries.values()) {
|
|
if (entry.sql === sql) {
|
|
entry.executedAt = new Date().toISOString();
|
|
entry.executionTimeMs = executionTimeMs;
|
|
entry.rowCount = rowCount;
|
|
this.save();
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
const entry: HistoryEntry = {
|
|
id: `h-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
sql,
|
|
executedAt: new Date().toISOString(),
|
|
executionTimeMs,
|
|
rowCount,
|
|
isFavorite: false,
|
|
};
|
|
this.entries.set(entry.id, entry);
|
|
|
|
// Limit: aelteste nicht-favorisierte loeschen
|
|
if (this.entries.size > MAX_ENTRIES) {
|
|
const sorted = this.getAll();
|
|
for (const e of sorted.slice(MAX_ENTRIES)) {
|
|
if (!e.isFavorite) this.entries.delete(e.id);
|
|
}
|
|
}
|
|
|
|
this.save();
|
|
return entry;
|
|
}
|
|
|
|
getAll(searchTerm?: string): HistoryEntry[] {
|
|
let results = Array.from(this.entries.values());
|
|
|
|
if (searchTerm) {
|
|
const term = searchTerm.toLowerCase();
|
|
results = results.filter(
|
|
e => e.sql.toLowerCase().includes(term) || e.name?.toLowerCase().includes(term),
|
|
);
|
|
}
|
|
|
|
return results.sort(
|
|
(a, b) => new Date(b.executedAt).getTime() - new Date(a.executedAt).getTime(),
|
|
);
|
|
}
|
|
|
|
toggleFavorite(id: string): void {
|
|
const e = this.entries.get(id);
|
|
if (e) { e.isFavorite = !e.isFavorite; this.save(); }
|
|
}
|
|
|
|
setName(id: string, name: string): void {
|
|
const e = this.entries.get(id);
|
|
if (e) { e.name = name || undefined; this.save(); }
|
|
}
|
|
|
|
delete(id: string): void {
|
|
this.entries.delete(id);
|
|
this.save();
|
|
}
|
|
|
|
clear(): void {
|
|
for (const [id, entry] of this.entries) {
|
|
if (!entry.isFavorite) this.entries.delete(id);
|
|
}
|
|
this.save();
|
|
}
|
|
}
|