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:
parent
f11eb45324
commit
36b25b321b
2 changed files with 356 additions and 0 deletions
99
src/main.ts
Normal file
99
src/main.ts
Normal 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
257
src/ui/settings-tab.ts
Normal 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`;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue