Query-History: automatisches Tracking aller Queries
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>
This commit is contained in:
parent
4547e0606e
commit
4b8f1fc814
1 changed files with 126 additions and 0 deletions
126
src/management/history.ts
Normal file
126
src/management/history.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue