Files
2026-04-06 00:57:13 +02:00

11 KiB

Ejemplos de dashboards

1. Dashboard minimo — una BD, un KPI

El dashboard mas simple posible: una conexion SQLite y un numero.

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.

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.

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).

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.

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

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

sections:
  - id: detail
    title: "Detalle (click para expandir)"
    collapsible: true
    widgets:
      - id: log_table
        type: table
        query: logs
        span: 12

Grid flexible por seccion

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 }