# Ejemplos de dashboards ## 1. Dashboard minimo — una BD, un KPI El dashboard mas simple posible: una conexion SQLite y un numero. ```yaml settings: title: "Contador" refresh: 5s columns: 4 theme: "dark" connections: db: driver: sqlite path: ./data.db queries: total: sql: "SELECT COUNT(*) as value FROM items" connection: db filters: {} sections: - id: main title: "Total" widgets: - id: count type: kpi title: "Items" query: total mapping: { value: "value" } span: 4 ``` --- ## 2. Dashboard con filtros — Postgres + select + date range Dashboard de ventas con filtros interactivos que re-ejecutan las queries. ```yaml settings: title: "Sales Dashboard" refresh: 30s columns: 12 theme: "dark" connections: sales: driver: postgres host: localhost port: 5432 user: analytics password: "${SALES_PW}" database: sales queries: revenue_kpi: connection: sales sql: | SELECT SUM(amount) as value, COUNT(*) as orders FROM orders WHERE created_at >= :from AND created_at <= :to AND (:cat = 'all' OR category = :cat) refresh: 15s params: from: "$filter.periodo.from" to: "$filter.periodo.to" cat: "$filter.categoria" revenue_daily: connection: sales sql: | SELECT date_trunc('day', created_at)::date as day, SUM(amount) as revenue FROM orders WHERE created_at >= :from AND (:cat = 'all' OR category = :cat) GROUP BY day ORDER BY day refresh: 30s params: from: "$filter.periodo.from" cat: "$filter.categoria" top_categories: connection: sales sql: | SELECT category, SUM(amount) as revenue FROM orders WHERE created_at >= :from GROUP BY category ORDER BY revenue DESC LIMIT 10 refresh: 30s params: from: "$filter.periodo.from" recent_orders: connection: sales sql: | SELECT id, customer, amount, category, status, created_at FROM orders ORDER BY created_at DESC LIMIT 25 refresh: 10s filters: periodo: type: date_range label: "Periodo" default: { from: "now-7d", to: "now" } presets: - { label: "Hoy", from: "now-0d", to: "now" } - { label: "7d", from: "now-7d", to: "now" } - { label: "30d", from: "now-30d", to: "now" } - { label: "90d", from: "now-90d", to: "now" } categoria: type: select label: "Categoria" default: "all" options: - { label: "Todas", value: "all" } - { label: "Electronics", value: "electronics" } - { label: "Clothing", value: "clothing" } - { label: "Food", value: "food" } sections: - id: kpis title: "Resumen" widgets: - id: revenue type: kpi title: "Revenue" query: revenue_kpi mapping: { value: "value", format: "$,.2f" } span: 4 - id: orders type: kpi title: "Ordenes" query: revenue_kpi mapping: { value: "orders", format: "," } span: 4 - id: charts title: "Tendencias" columns: 2 widgets: - id: revenue_line type: line_chart title: "Revenue diario" query: revenue_daily mapping: x: "day" series: [{ key: "revenue", name: "Revenue" }] options: { curve: monotone, zoomable: true } span: 1 - id: categories_bar type: bar_chart title: "Top categorias" query: top_categories mapping: { x: "category", y: "revenue" } span: 1 - id: detail title: "Ordenes recientes" collapsible: true widgets: - id: orders_table type: table title: "Ultimas 25" query: recent_orders mapping: columns: - { key: "id", label: "ID" } - { key: "customer", label: "Cliente" } - { key: "amount", label: "Monto", format: "$,.2f" } - { key: "category", label: "Categoria" } - { key: "status", label: "Estado" } - { key: "created_at", label: "Fecha", format: "datetime" } span: 12 ``` --- ## 3. Dashboard multi-BD — SQLite + DuckDB Combina datos de distintas bases en un solo dashboard. ```yaml settings: title: "Multi-DB Analytics" refresh: 10s columns: 12 theme: "dark" connections: ops: driver: sqlite path: ./operations.db warehouse: driver: duckdb path: ./analytics.duckdb queries: entities_count: connection: ops sql: "SELECT COUNT(*) as value FROM entities" refresh: 5s executions_count: connection: ops sql: "SELECT COUNT(*) as value FROM executions" refresh: 5s assertions_pass_rate: connection: ops sql: | SELECT ROUND(100.0 * SUM(CASE WHEN passed = 1 THEN 1 ELSE 0 END) / COUNT(*), 1) as value FROM assertion_results refresh: 10s daily_events: connection: warehouse sql: | SELECT date_trunc('day', ts) as day, COUNT(*) as events FROM events GROUP BY day ORDER BY day DESC LIMIT 30 refresh: 30s status_distribution: connection: ops sql: "SELECT status, COUNT(*) as count FROM entities GROUP BY status" refresh: 10s filters: {} sections: - id: kpis title: "Operations" widgets: - id: entities type: kpi title: "Entities" query: entities_count mapping: { value: "value" } span: 4 - id: executions type: kpi title: "Executions" query: executions_count mapping: { value: "value" } span: 4 - id: pass_rate type: kpi title: "Assertion Pass %" query: assertions_pass_rate mapping: { value: "value" } span: 4 - id: charts title: "Visualizacion" columns: 2 widgets: - id: events_area type: area_chart title: "Eventos diarios (DuckDB)" query: daily_events mapping: x: "day" series: [{ key: "events", name: "Eventos" }] options: { show_grid: true } span: 1 - id: status_bar type: bar_chart title: "Distribucion por status (SQLite)" query: status_distribution mapping: { x: "status", y: "count" } span: 1 ``` --- ## 4. Dashboard tiempo real — refresh sub-segundo Para metricas que cambian rapido (CPU, colas, precios). ```yaml settings: title: "Realtime Monitor" refresh: 1s columns: 12 theme: "dark" connections: metrics: driver: sqlite path: ./metrics.db queries: cpu_current: connection: metrics sql: "SELECT value FROM metrics WHERE key = 'cpu' ORDER BY ts DESC LIMIT 1" refresh: 200ms stale_time: 100ms memory_current: connection: metrics sql: "SELECT value FROM metrics WHERE key = 'memory' ORDER BY ts DESC LIMIT 1" refresh: 500ms stale_time: 250ms cpu_history: connection: metrics sql: | SELECT ts, value FROM metrics WHERE key = 'cpu' ORDER BY ts DESC LIMIT 60 refresh: 1s queue_depth: connection: metrics sql: "SELECT value FROM metrics WHERE key = 'queue' ORDER BY ts DESC LIMIT 1" refresh: 300ms stale_time: 150ms filters: {} sections: - id: live title: "Live" widgets: - id: cpu type: kpi title: "CPU %" query: cpu_current mapping: { value: "value" } span: 4 - id: mem type: kpi title: "Memory %" query: memory_current mapping: { value: "value" } span: 4 - id: queue type: kpi title: "Queue" query: queue_depth mapping: { value: "value" } span: 4 - id: history title: "CPU History (60s)" widgets: - id: cpu_line type: line_chart title: "CPU % (ultimo minuto)" query: cpu_history mapping: x: "ts" y: "value" options: { curve: linear, show_grid: true, height: 250 } span: 12 ``` --- ## 5. Dashboard con busqueda — filtro texto Dashboard que permite buscar texto con debounce. ```yaml settings: title: "Buscador de funciones" refresh: 10s columns: 12 theme: "dark" connections: registry: driver: sqlite path: ../../../registry.db queries: search_results: connection: registry sql: | SELECT id, kind, lang, domain, description FROM functions WHERE id LIKE '%' || :q || '%' OR description LIKE '%' || :q || '%' ORDER BY updated_at DESC LIMIT 50 refresh: 10s params: q: "$filter.busqueda" result_count: connection: registry sql: | SELECT COUNT(*) as value FROM functions WHERE id LIKE '%' || :q || '%' OR description LIKE '%' || :q || '%' refresh: 10s params: q: "$filter.busqueda" filters: busqueda: type: text label: "Buscar" default: "" placeholder: "nombre o descripcion..." debounce: 300 sections: - id: results title: "Resultados" widgets: - id: count type: kpi title: "Coincidencias" query: result_count mapping: { value: "value" } span: 3 - id: results_table type: table title: "Funciones encontradas" query: search_results mapping: columns: - { key: "id", label: "ID" } - { key: "kind", label: "Kind" } - { key: "lang", label: "Lang" } - { key: "domain", label: "Domain" } - { key: "description", label: "Descripcion" } span: 12 ``` --- ## Patrones comunes ### Una query alimenta varios widgets ```yaml queries: summary: sql: "SELECT SUM(amount) as revenue, COUNT(*) as orders, AVG(amount) as avg FROM orders" sections: - id: kpis title: "KPIs" widgets: - id: rev type: kpi query: summary mapping: { value: "revenue", format: "$,.2f" } span: 4 - id: ord type: kpi query: summary mapping: { value: "orders", format: "," } span: 4 - id: avg type: kpi query: summary mapping: { value: "avg", format: "$,.2f" } span: 4 ``` La query se ejecuta una sola vez y los 3 KPIs leen del mismo resultado. ### Seccion colapsable para detalle ```yaml sections: - id: detail title: "Detalle (click para expandir)" collapsible: true widgets: - id: log_table type: table query: logs span: 12 ``` ### Grid flexible por seccion ```yaml sections: - id: kpis title: "KPIs" # hereda columns: 12 del global → 4 KPIs de span 3 widgets: - { id: a, type: kpi, query: q, mapping: { value: v }, span: 3 } - { id: b, type: kpi, query: q, mapping: { value: v }, span: 3 } - { id: c, type: kpi, query: q, mapping: { value: v }, span: 3 } - { id: d, type: kpi, query: q, mapping: { value: v }, span: 3 } - id: charts title: "Graficos" columns: 2 # override: solo 2 columnas widgets: - { id: chart1, type: line_chart, query: q1, mapping: { x: a, y: b }, span: 1 } - { id: chart2, type: bar_chart, query: q2, mapping: { x: a, y: b }, span: 1 } - id: full title: "Tabla" columns: 1 # 1 columna = ancho completo widgets: - { id: tbl, type: table, query: q3, span: 1 } ```