Plugin-Einstieg und Settings-Tab

Minimaler main.ts: onload/onunload, DB-Initialisierung,
Settings laden/speichern, shouldTrack-Filter mit Glob-Matching.
Settings-Tab mit Tracking-Toggles, Exclude-Patterns,
erweitertem Bereich für Performance/Retention/DB-Info.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca Oelfke 2026-02-12 10:46:02 +01:00
parent f11eb45324
commit 36b25b321b
2 changed files with 356 additions and 0 deletions

99
src/main.ts Normal file
View file

@ -0,0 +1,99 @@
import { Plugin, FileSystemAdapter, TFile } from 'obsidian';
import { LogfireSettings, DEFAULT_SETTINGS, deepMerge } from './types';
import { DatabaseManager } from './core/database';
import { LogfireSettingTab } from './ui/settings-tab';
export default class LogfirePlugin extends Plugin {
settings!: LogfireSettings;
db!: DatabaseManager;
async onload(): Promise<void> {
console.log('[Logfire] Lade Plugin...');
await this.loadSettings();
const dbPath = this.getDatabasePath();
this.db = new DatabaseManager(dbPath, this.settings);
this.addSettingTab(new LogfireSettingTab(this.app, this));
console.log('[Logfire] Plugin geladen.');
}
async onunload(): Promise<void> {
console.log('[Logfire] Entlade Plugin...');
if (this.db) {
this.db.close();
}
console.log('[Logfire] Plugin entladen.');
}
// ---------------------------------------------------------------------------
// Track filtering (used by collectors in later features)
// ---------------------------------------------------------------------------
shouldTrack(path: string): boolean {
if (!this.settings.general.enabled) return false;
const logFolder = this.settings.general.logFolder;
if (path.startsWith(logFolder + '/') || path === logFolder) return false;
if (path.startsWith('.obsidian/')) return false;
for (const folder of this.settings.tracking.excludedFolders) {
if (path.startsWith(folder + '/') || path === folder) return false;
}
for (const pattern of this.settings.tracking.excludedPatterns) {
if (matchGlob(path, pattern)) return false;
}
const file = this.app.vault.getAbstractFileByPath(path);
if (file instanceof TFile) {
const cache = this.app.metadataCache.getFileCache(file);
if (cache?.frontmatter?.logfire === false) return false;
}
return true;
}
// ---------------------------------------------------------------------------
// Settings
// ---------------------------------------------------------------------------
private async loadSettings(): Promise<void> {
const saved = (await this.loadData()) as Partial<LogfireSettings> | null;
this.settings = deepMerge(DEFAULT_SETTINGS, saved ?? {});
}
async saveSettings(): Promise<void> {
await this.saveData(this.settings);
}
// ---------------------------------------------------------------------------
// Database path
// ---------------------------------------------------------------------------
private getDatabasePath(): string {
const adapter = this.app.vault.adapter;
if (!(adapter instanceof FileSystemAdapter)) {
throw new Error('[Logfire] Benötigt einen Desktop-Vault mit Dateisystem-Zugriff.');
}
const basePath = adapter.getBasePath();
return `${basePath}/${this.app.vault.configDir}/plugins/logfire/logfire.db`;
}
}
// ---------------------------------------------------------------------------
// Simple glob matching (supports * and **)
// ---------------------------------------------------------------------------
function matchGlob(path: string, pattern: string): boolean {
const regex = pattern
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
.replace(/\*\*/g, '{{GLOBSTAR}}')
.replace(/\*/g, '[^/]*')
.replace(/{{GLOBSTAR}}/g, '.*');
return new RegExp(`^${regex}$`).test(path);
}

257
src/ui/settings-tab.ts Normal file
View file

@ -0,0 +1,257 @@
import { App, PluginSettingTab, Setting, Notice } from 'obsidian';
import type LogfirePlugin from '../main';
export class LogfireSettingTab extends PluginSettingTab {
constructor(app: App, private plugin: LogfirePlugin) {
super(app, plugin);
}
display(): void {
const { containerEl } = this;
containerEl.empty();
// ----- General -----
containerEl.createEl('h2', { text: 'Allgemein' });
new Setting(containerEl)
.setName('Tracking aktiviert')
.setDesc('Hauptschalter für alle Event-Collector.')
.addToggle(t => t
.setValue(this.plugin.settings.general.enabled)
.onChange(async (v) => {
this.plugin.settings.general.enabled = v;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Log-Ordner')
.setDesc('Ordner für Markdown-Projektionen. Wird automatisch vom Tracking ausgeschlossen.')
.addText(t => t
.setPlaceholder('Logfire')
.setValue(this.plugin.settings.general.logFolder)
.onChange(async (v) => {
this.plugin.settings.general.logFolder = v || 'Logfire';
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Beim Start pausieren')
.setDesc('Startet im pausierten Zustand — keine Events bis manuell fortgesetzt.')
.addToggle(t => t
.setValue(this.plugin.settings.general.pauseOnStartup)
.onChange(async (v) => {
this.plugin.settings.general.pauseOnStartup = v;
await this.plugin.saveSettings();
}));
// ----- Tracking -----
containerEl.createEl('h2', { text: 'Tracking' });
new Setting(containerEl)
.setName('Datei-Events')
.setDesc('Erstellen, Löschen, Umbenennen, Verschieben und Ändern von Dateien.')
.addToggle(t => t
.setValue(this.plugin.settings.tracking.fileEvents)
.onChange(async (v) => {
this.plugin.settings.tracking.fileEvents = v;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Content-Analyse')
.setDesc('Semantische Diffs: Wortzählung, Links, Tags, Überschriften, Frontmatter.')
.addToggle(t => t
.setValue(this.plugin.settings.tracking.contentAnalysis)
.onChange(async (v) => {
this.plugin.settings.tracking.contentAnalysis = v;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Navigation')
.setDesc('Aktive Datei-Wechsel, Datei öffnen/schließen mit Dauer.')
.addToggle(t => t
.setValue(this.plugin.settings.tracking.navigation)
.onChange(async (v) => {
this.plugin.settings.tracking.navigation = v;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Editor-Änderungen')
.setDesc('Tastenanschläge (debounced und aggregiert).')
.addToggle(t => t
.setValue(this.plugin.settings.tracking.editorChanges)
.onChange(async (v) => {
this.plugin.settings.tracking.editorChanges = v;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Kommando-Tracking')
.setDesc('Ausführung von Befehlen über Palette und Hotkeys.')
.addToggle(t => t
.setValue(this.plugin.settings.tracking.commandTracking)
.onChange(async (v) => {
this.plugin.settings.tracking.commandTracking = v;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Ausgeschlossene Ordner')
.setDesc('Komma-getrennte Liste von Ordnern, die nicht getrackt werden.')
.addText(t => t
.setPlaceholder('.obsidian, templates')
.setValue(this.plugin.settings.tracking.excludedFolders.join(', '))
.onChange(async (v) => {
this.plugin.settings.tracking.excludedFolders = v
.split(',')
.map(s => s.trim())
.filter(s => s.length > 0);
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Ausgeschlossene Patterns')
.setDesc('Komma-getrennte Glob-Patterns (z.B. **/*.excalidraw.md).')
.addText(t => t
.setPlaceholder('**/*.excalidraw.md')
.setValue(this.plugin.settings.tracking.excludedPatterns.join(', '))
.onChange(async (v) => {
this.plugin.settings.tracking.excludedPatterns = v
.split(',')
.map(s => s.trim())
.filter(s => s.length > 0);
await this.plugin.saveSettings();
}));
// ----- Advanced (collapsible) -----
const advancedHeader = containerEl.createEl('details');
advancedHeader.createEl('summary', { text: 'Erweiterte Einstellungen' })
.style.cursor = 'pointer';
const advEl = advancedHeader.createDiv();
advEl.createEl('p', {
text: 'Diese Einstellungen beeinflussen Performance und Speicher. Die Standardwerte sind für die meisten Vaults optimal.',
cls: 'setting-item-description',
});
// Performance
advEl.createEl('h3', { text: 'Performance' });
new Setting(advEl)
.setName('Editor-Debounce (ms)')
.setDesc('Wartezeit nach letztem Tastenanschlag vor editor:change Event.')
.addText(t => t
.setValue(String(this.plugin.settings.advanced.debounceMs))
.onChange(async (v) => {
const n = parseInt(v, 10);
if (!isNaN(n) && n >= 500) {
this.plugin.settings.advanced.debounceMs = n;
await this.plugin.saveSettings();
}
}));
new Setting(advEl)
.setName('Flush-Intervall (ms)')
.setDesc('Wie oft gepufferte Events in die Datenbank geschrieben werden.')
.addText(t => t
.setValue(String(this.plugin.settings.advanced.flushIntervalMs))
.onChange(async (v) => {
const n = parseInt(v, 10);
if (!isNaN(n) && n >= 1000) {
this.plugin.settings.advanced.flushIntervalMs = n;
await this.plugin.saveSettings();
}
}));
new Setting(advEl)
.setName('Flush-Schwellwert')
.setDesc('Maximale Events im Puffer vor erzwungenem Schreiben.')
.addText(t => t
.setValue(String(this.plugin.settings.advanced.flushThreshold))
.onChange(async (v) => {
const n = parseInt(v, 10);
if (!isNaN(n) && n >= 10) {
this.plugin.settings.advanced.flushThreshold = n;
await this.plugin.saveSettings();
}
}));
// Retention
advEl.createEl('h3', { text: 'Aufbewahrung' });
new Setting(advEl)
.setName('Raw-Events aufbewahren (Tage)')
.setDesc('Danach werden Events in Tages-Statistiken aggregiert und gelöscht.')
.addText(t => t
.setValue(String(this.plugin.settings.advanced.retention.rawEventsDays))
.onChange(async (v) => {
const n = parseInt(v, 10);
if (!isNaN(n) && n >= 1) {
this.plugin.settings.advanced.retention.rawEventsDays = n;
await this.plugin.saveSettings();
}
}));
new Setting(advEl)
.setName('Tages-Statistiken aufbewahren (Tage)')
.setDesc('Danach werden Tages-Statistiken in Monats-Statistiken aggregiert.')
.addText(t => t
.setValue(String(this.plugin.settings.advanced.retention.dailyStatsDays))
.onChange(async (v) => {
const n = parseInt(v, 10);
if (!isNaN(n) && n >= 30) {
this.plugin.settings.advanced.retention.dailyStatsDays = n;
await this.plugin.saveSettings();
}
}));
new Setting(advEl)
.setName('Wartung beim Start')
.setDesc('Automatisch Retention-Cleanup beim Plugin-Start ausführen.')
.addToggle(t => t
.setValue(this.plugin.settings.advanced.retention.maintenanceOnStartup)
.onChange(async (v) => {
this.plugin.settings.advanced.retention.maintenanceOnStartup = v;
await this.plugin.saveSettings();
}));
// Database info
advEl.createEl('h3', { text: 'Datenbank' });
const dbInfoEl = advEl.createDiv();
try {
const eventCount = this.plugin.db.getEventCount();
const dbSize = this.plugin.db.getDatabaseSizeBytes();
const oldest = this.plugin.db.getOldestEventTimestamp();
dbInfoEl.createEl('p', {
text: `Events: ${eventCount.toLocaleString()} | Größe: ${formatBytes(dbSize)} | Ältestes: ${oldest ? new Date(oldest).toLocaleDateString() : 'k.A.'}`,
});
} catch {
dbInfoEl.createEl('p', { text: 'Datenbank-Info nicht verfügbar.' });
}
new Setting(advEl)
.setName('Datenbank-Aktionen')
.addButton(b => b
.setButtonText('Wartung ausführen')
.onClick(() => {
try {
this.plugin.db.runMaintenance(this.plugin.settings.advanced.retention);
new Notice('Logfire: Wartung abgeschlossen.');
this.display();
} catch (err) {
new Notice('Logfire: Wartung fehlgeschlagen.');
console.error('[Logfire] Wartung fehlgeschlagen:', err);
}
}));
}
}
function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}