Processor: Chart-Support und Dashboard-Block-Prozessor

logfire-sql Bloecke unterstuetzen jetzt -- chart: Direktiven
fuer Inline-Visualisierung. Neuer logfire-dashboard Block-
Prozessor rendert Multi-Widget-Dashboards direkt in Notizen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca Oelfke 2026-02-12 11:17:22 +01:00
parent eec66b738d
commit aba060f3a5

View file

@ -3,6 +3,9 @@ import { QueryConfig, TimeRange, EventType, EventCategory } from '../types';
import { buildQuery } from '../core/query-builder'; import { buildQuery } from '../core/query-builder';
import { DatabaseManager } from '../core/database'; import { DatabaseManager } from '../core/database';
import { renderTable, renderTimeline, renderSummary, renderMetric, renderList, renderHeatmap, formatValue } from '../viz/table-renderer'; import { renderTable, renderTimeline, renderSummary, renderMetric, renderList, renderHeatmap, formatValue } from '../viz/table-renderer';
import { renderChart, parseChartConfig } from '../viz/chart-renderer';
import { parseDashboardBlock, DashboardView, DASHBOARD_VIEW_TYPE } from '../viz/dashboard';
import type LogfirePlugin from '../main';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Refresh timer management // Refresh timer management
@ -84,13 +87,20 @@ export function registerLogfireSqlBlock(
return; return;
} }
const chartConfig = parseChartConfig(source);
try { try {
const rows = db.queryReadOnly(sql) as Record<string, unknown>[]; const rows = db.queryReadOnly(sql) as Record<string, unknown>[];
if (!Array.isArray(rows) || rows.length === 0) { if (!Array.isArray(rows) || rows.length === 0) {
el.createEl('p', { text: 'Keine Ergebnisse.', cls: 'logfire-empty' }); el.createEl('p', { text: 'Keine Ergebnisse.', cls: 'logfire-empty' });
return; return;
} }
if (chartConfig) {
renderChart(el, rows, chartConfig);
} else {
renderTable(el, rows); renderTable(el, rows);
}
if (refresh && refresh > 0) { if (refresh && refresh > 0) {
setupRefreshTimer(el, () => { setupRefreshTimer(el, () => {
@ -101,7 +111,11 @@ export function registerLogfireSqlBlock(
el.createEl('p', { text: 'Keine Ergebnisse.', cls: 'logfire-empty' }); el.createEl('p', { text: 'Keine Ergebnisse.', cls: 'logfire-empty' });
return; return;
} }
if (chartConfig) {
renderChart(el, freshRows, chartConfig);
} else {
renderTable(el, freshRows); renderTable(el, freshRows);
}
} catch (err) { } catch (err) {
renderError(el, err); renderError(el, err);
} }
@ -113,6 +127,68 @@ export function registerLogfireSqlBlock(
}); });
} }
// ---------------------------------------------------------------------------
// `logfire-dashboard` block — Dashboard Code-Block
// ---------------------------------------------------------------------------
export function registerLogfireDashboardBlock(
plugin: LogfirePlugin,
db: DatabaseManager,
registerFn: (language: string, handler: (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => void) => void,
): void {
registerFn('logfire-dashboard', (source, el, ctx) => {
const dashboard = parseDashboardBlock(source);
if (!dashboard) {
renderError(el, new Error('Ungültige Dashboard-Definition.'));
return;
}
// Render dashboard inline
const wrapper = el.createDiv({ cls: 'logfire-dash-inline' });
if (dashboard.name) {
wrapper.createDiv({ cls: 'logfire-dash-inline-title', text: dashboard.name });
}
const grid = wrapper.createDiv({ cls: 'logfire-dash-grid' });
grid.style.gridTemplateColumns = `repeat(${dashboard.columns}, 1fr)`;
for (const widget of dashboard.widgets) {
const widgetEl = grid.createDiv({ cls: 'logfire-dash-widget' });
widgetEl.style.gridColumn = `${widget.position.col + 1} / span ${widget.position.width}`;
widgetEl.style.gridRow = `${widget.position.row + 1} / span ${widget.position.height}`;
if (widget.title) {
widgetEl.createDiv({ cls: 'logfire-dash-widget-title', text: widget.title });
}
const content = widgetEl.createDiv({ cls: 'logfire-dash-widget-content' });
try {
if (widget.type === 'text' && widget.text) {
content.createDiv({ text: widget.text });
} else if (widget.sql) {
const rows = db.queryReadOnly(widget.sql) as Record<string, unknown>[];
if (rows.length === 0) {
content.createDiv({ cls: 'logfire-empty', text: 'Keine Ergebnisse.' });
} else if (widget.type === 'chart' && widget.chartConfig) {
renderChart(content, rows, widget.chartConfig);
} else if (widget.type === 'stat') {
renderMetric(content, rows);
} else {
renderTable(content, rows);
}
}
} catch (err) {
content.createDiv({
cls: 'logfire-error',
text: `Fehler: ${err instanceof Error ? err.message : String(err)}`,
});
}
}
});
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// YAML config parsing // YAML config parsing
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------